Merge "wgGroupPermissions: Don't grant editmyusercss/editmyuserjs to anons"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 23 Feb 2018 22:59:29 +0000 (22:59 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 23 Feb 2018 22:59:29 +0000 (22:59 +0000)
379 files changed:
RELEASE-NOTES-1.31
autoload.php
includes/ActorMigration.php [new file with mode: 0644]
includes/Block.php
includes/DefaultSettings.php
includes/EditPage.php
includes/Linker.php
includes/MediaWikiServices.php
includes/ProtectionForm.php
includes/Revision.php
includes/RevisionList.php
includes/ServiceWiring.php
includes/Storage/BlobStore.php
includes/Storage/RevisionStore.php
includes/Storage/SqlBlobStore.php
includes/Title.php
includes/actions/InfoAction.php
includes/api/ApiQueryAllDeletedRevisions.php
includes/api/ApiQueryAllImages.php
includes/api/ApiQueryAllRevisions.php
includes/api/ApiQueryAllUsers.php
includes/api/ApiQueryBase.php
includes/api/ApiQueryBlocks.php
includes/api/ApiQueryContributors.php
includes/api/ApiQueryDeletedRevisions.php
includes/api/ApiQueryDeletedrevs.php
includes/api/ApiQueryLogEvents.php
includes/api/ApiQueryRecentChanges.php
includes/api/ApiQueryRevisions.php
includes/api/ApiQueryUserContributions.php
includes/api/ApiStashEdit.php
includes/api/i18n/ru.json
includes/cache/UserCache.php
includes/changes/ChangesList.php
includes/changes/RecentChange.php
includes/changetags/ChangeTagsLogItem.php
includes/content/ContentHandler.php
includes/db/DatabaseOracle.php
includes/deferred/SiteStatsUpdate.php
includes/exception/CannotCreateActorException.php [new file with mode: 0644]
includes/export/WikiExporter.php
includes/externalstore/ExternalStore.php
includes/externalstore/ExternalStoreDB.php
includes/externalstore/ExternalStoreHttp.php
includes/externalstore/ExternalStoreMedium.php
includes/externalstore/ExternalStoreMwstore.php
includes/filerepo/file/ArchivedFile.php
includes/filerepo/file/LocalFile.php
includes/filerepo/file/OldLocalFile.php
includes/import/WikiRevision.php
includes/installer/DatabaseUpdater.php
includes/installer/MssqlUpdater.php
includes/installer/MysqlUpdater.php
includes/installer/OracleUpdater.php
includes/installer/PostgresUpdater.php
includes/installer/SqliteUpdater.php
includes/installer/i18n/es.json
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/libs/CSSMin.php
includes/libs/objectcache/WANObjectCache.php
includes/libs/rdbms/ChronologyProtector.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/database/position/DBMasterPos.php
includes/libs/rdbms/database/position/MySQLMasterPos.php
includes/logging/LogEntry.php
includes/logging/LogPage.php
includes/logging/LogPager.php
includes/media/JpegMetadataExtractor.php
includes/page/WikiPage.php
includes/resourceloader/ResourceLoaderStartUpModule.php
includes/revisiondelete/RevDelArchiveItem.php
includes/revisiondelete/RevDelArchivedFileItem.php
includes/revisiondelete/RevDelFileItem.php
includes/revisiondelete/RevDelList.php
includes/revisiondelete/RevDelLogItem.php
includes/revisiondelete/RevDelLogList.php
includes/revisiondelete/RevDelRevisionItem.php
includes/revisiondelete/RevisionDeleteUser.php
includes/shell/Command.php
includes/specialpage/ChangesListSpecialPage.php
includes/specials/SpecialFileDuplicateSearch.php
includes/specials/SpecialLog.php
includes/specials/SpecialMIMEsearch.php
includes/specials/SpecialNewpages.php
includes/specials/SpecialRecentchangeslinked.php
includes/specials/SpecialRedirect.php
includes/specials/pagers/ActiveUsersPager.php
includes/specials/pagers/BlockListPager.php
includes/specials/pagers/ContribsPager.php
includes/specials/pagers/DeletedContribsPager.php
includes/specials/pagers/ImageListPager.php
includes/specials/pagers/NewFilesPager.php
includes/specials/pagers/NewPagesPager.php
includes/specials/pagers/ProtectedPagesPager.php
includes/user/User.php
includes/user/UserIdentity.php
includes/user/UserIdentityValue.php
includes/watcheditem/WatchedItemQueryService.php
jsduck.json
languages/i18n/aeb-arab.json
languages/i18n/af.json
languages/i18n/ais.json
languages/i18n/aln.json
languages/i18n/am.json
languages/i18n/an.json
languages/i18n/ar.json
languages/i18n/arq.json
languages/i18n/ary.json
languages/i18n/arz.json
languages/i18n/as.json
languages/i18n/ast.json
languages/i18n/awa.json
languages/i18n/az.json
languages/i18n/azb.json
languages/i18n/ba.json
languages/i18n/bcc.json
languages/i18n/bcl.json
languages/i18n/be-tarask.json
languages/i18n/be.json
languages/i18n/bg.json
languages/i18n/bgn.json
languages/i18n/bho.json
languages/i18n/bjn.json
languages/i18n/bn.json
languages/i18n/bpy.json
languages/i18n/br.json
languages/i18n/bs.json
languages/i18n/ca.json
languages/i18n/ce.json
languages/i18n/ckb.json
languages/i18n/crh-cyrl.json
languages/i18n/crh-latn.json
languages/i18n/cs.json
languages/i18n/csb.json
languages/i18n/cy.json
languages/i18n/da.json
languages/i18n/de-ch.json
languages/i18n/de-formal.json
languages/i18n/de.json
languages/i18n/diq.json
languages/i18n/dsb.json
languages/i18n/dtp.json
languages/i18n/dty.json
languages/i18n/egl.json
languages/i18n/el.json
languages/i18n/eo.json
languages/i18n/es.json
languages/i18n/et.json
languages/i18n/eu.json
languages/i18n/ext.json
languages/i18n/fa.json
languages/i18n/fi.json
languages/i18n/fo.json
languages/i18n/fr.json
languages/i18n/frc.json
languages/i18n/frp.json
languages/i18n/frr.json
languages/i18n/fy.json
languages/i18n/ga.json
languages/i18n/gan-hans.json
languages/i18n/gan-hant.json
languages/i18n/gd.json
languages/i18n/gl.json
languages/i18n/grc.json
languages/i18n/gsw.json
languages/i18n/gu.json
languages/i18n/hak.json
languages/i18n/he.json
languages/i18n/hi.json
languages/i18n/hif-latn.json
languages/i18n/hil.json
languages/i18n/hr.json
languages/i18n/hrx.json
languages/i18n/hsb.json
languages/i18n/ht.json
languages/i18n/hu.json
languages/i18n/hy.json
languages/i18n/ia.json
languages/i18n/id.json
languages/i18n/ie.json
languages/i18n/ilo.json
languages/i18n/io.json
languages/i18n/is.json
languages/i18n/it.json
languages/i18n/ja.json
languages/i18n/jam.json
languages/i18n/jut.json
languages/i18n/jv.json
languages/i18n/ka.json
languages/i18n/kab.json
languages/i18n/kbd-cyrl.json
languages/i18n/kiu.json
languages/i18n/kk-arab.json
languages/i18n/kk-cyrl.json
languages/i18n/kk-latn.json
languages/i18n/km.json
languages/i18n/ko.json
languages/i18n/krc.json
languages/i18n/ksh.json
languages/i18n/ku-latn.json
languages/i18n/kum.json
languages/i18n/lb.json
languages/i18n/lfn.json
languages/i18n/lg.json
languages/i18n/li.json
languages/i18n/lij.json
languages/i18n/lki.json
languages/i18n/lmo.json
languages/i18n/lrc.json
languages/i18n/lt.json
languages/i18n/lus.json
languages/i18n/lv.json
languages/i18n/lzh.json
languages/i18n/mai.json
languages/i18n/map-bms.json
languages/i18n/mdf.json
languages/i18n/mg.json
languages/i18n/min.json
languages/i18n/mk.json
languages/i18n/ml.json
languages/i18n/mn.json
languages/i18n/mr.json
languages/i18n/ms.json
languages/i18n/mt.json
languages/i18n/mwl.json
languages/i18n/nan.json
languages/i18n/nap.json
languages/i18n/nb.json
languages/i18n/nds-nl.json
languages/i18n/nds.json
languages/i18n/ne.json
languages/i18n/nl-informal.json
languages/i18n/nl.json
languages/i18n/nn.json
languages/i18n/oc.json
languages/i18n/or.json
languages/i18n/os.json
languages/i18n/pa.json
languages/i18n/pam.json
languages/i18n/pl.json
languages/i18n/pms.json
languages/i18n/pnb.json
languages/i18n/prg.json
languages/i18n/ps.json
languages/i18n/pt-br.json
languages/i18n/pt.json
languages/i18n/qu.json
languages/i18n/rm.json
languages/i18n/ro.json
languages/i18n/roa-tara.json
languages/i18n/ru.json
languages/i18n/rue.json
languages/i18n/sa.json
languages/i18n/sah.json
languages/i18n/sat.json
languages/i18n/scn.json
languages/i18n/sco.json
languages/i18n/sd.json
languages/i18n/sdc.json
languages/i18n/sei.json
languages/i18n/ses.json
languages/i18n/sgs.json
languages/i18n/sh.json
languages/i18n/shn.json
languages/i18n/si.json
languages/i18n/sk.json
languages/i18n/sl.json
languages/i18n/sli.json
languages/i18n/sq.json
languages/i18n/sr-ec.json
languages/i18n/sr-el.json
languages/i18n/stq.json
languages/i18n/su.json
languages/i18n/sv.json
languages/i18n/sw.json
languages/i18n/szl.json
languages/i18n/ta.json
languages/i18n/te.json
languages/i18n/tg-cyrl.json
languages/i18n/tg-latn.json
languages/i18n/th.json
languages/i18n/tk.json
languages/i18n/tl.json
languages/i18n/tr.json
languages/i18n/tt-cyrl.json
languages/i18n/tt-latn.json
languages/i18n/ug-arab.json
languages/i18n/uk.json
languages/i18n/ur.json
languages/i18n/uz.json
languages/i18n/vec.json
languages/i18n/vep.json
languages/i18n/vi.json
languages/i18n/vo.json
languages/i18n/vro.json
languages/i18n/wa.json
languages/i18n/war.json
languages/i18n/wo.json
languages/i18n/wuu.json
languages/i18n/xal.json
languages/i18n/xmf.json
languages/i18n/yi.json
languages/i18n/yo.json
languages/i18n/yue.json
languages/i18n/zea.json
languages/i18n/zh-hans.json
languages/i18n/zh-hant.json
maintenance/archives/patch-actor-table.sql [new file with mode: 0644]
maintenance/deleteDefaultMessages.php
maintenance/fixUserRegistration.php
maintenance/initEditCount.php
maintenance/migrateActors.php [new file with mode: 0644]
maintenance/mssql/archives/patch-actor-table.sql [new file with mode: 0644]
maintenance/mssql/tables.sql
maintenance/oracle/archives/patch-actor-table.sql [new file with mode: 0644]
maintenance/oracle/tables.sql
maintenance/orphans.php
maintenance/populateIpChanges.php
maintenance/populateLogSearch.php
maintenance/populateLogUsertext.php
maintenance/postgres/archives/patch-actor-table.sql [new file with mode: 0644]
maintenance/postgres/tables.sql
maintenance/reassignEdits.php
maintenance/rebuildrecentchanges.php
maintenance/removeUnusedAccounts.php
maintenance/rollbackEdits.php
maintenance/sqlite/archives/patch-actor-table.sql [new file with mode: 0644]
maintenance/tables.sql
resources/Resources.php
resources/src/jquery/jquery.byteLimit.js [deleted file]
resources/src/jquery/jquery.lengthLimit.js [new file with mode: 0644]
resources/src/mediawiki.action/mediawiki.action.edit.js
resources/src/mediawiki.legacy/protect.js
resources/src/mediawiki.less/mediawiki.mixins.less
resources/src/mediawiki.rcfilters/mw.rcfilters.Controller.js
resources/src/mediawiki.rcfilters/mw.rcfilters.init.js
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.ChangesListWrapperWidget.js
resources/src/mediawiki.special/mediawiki.special.unwatchedPages.css
resources/src/mediawiki.special/mediawiki.special.unwatchedPages.js
resources/src/mediawiki.widgets.visibleByteLimit/mediawiki.widgets.visibleByteLimit.js [deleted file]
resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js [new file with mode: 0644]
resources/src/mediawiki/mediawiki.String.js
tests/parser/ParserTestRunner.php
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/data/media/jpeg-segment-loop1.jpg [new file with mode: 0644]
tests/phpunit/data/media/jpeg-segment-loop2.jpg [new file with mode: 0644]
tests/phpunit/data/media/jpeg-xmp-loop.jpg [deleted file]
tests/phpunit/includes/ActorMigrationTest.php [new file with mode: 0644]
tests/phpunit/includes/BlockTest.php
tests/phpunit/includes/CommentStoreTest.php
tests/phpunit/includes/PageArchiveTest.php
tests/phpunit/includes/RevisionDbTestBase.php
tests/phpunit/includes/RevisionTest.php
tests/phpunit/includes/Storage/RevisionStoreDbTest.php
tests/phpunit/includes/Storage/RevisionStoreRecordTest.php
tests/phpunit/includes/Storage/RevisionStoreTest.php
tests/phpunit/includes/api/ApiBaseTest.php
tests/phpunit/includes/api/ApiQueryRecentChangesIntegrationTest.php
tests/phpunit/includes/api/ApiQueryWatchlistIntegrationTest.php
tests/phpunit/includes/api/query/ApiQueryUserContributionsTest.php [new file with mode: 0644]
tests/phpunit/includes/auth/AuthManagerTest.php
tests/phpunit/includes/auth/CheckBlocksSecondaryAuthenticationProviderTest.php
tests/phpunit/includes/changes/RecentChangeTest.php
tests/phpunit/includes/import/ImportTest.php
tests/phpunit/includes/libs/CSSMinTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseMysqlBaseTest.php
tests/phpunit/includes/logging/LogFormatterTestCase.php
tests/phpunit/includes/media/JpegMetadataExtractorTest.php
tests/phpunit/includes/page/WikiPageDbTestBase.php
tests/phpunit/includes/password/UserPasswordPolicyTest.php
tests/phpunit/includes/shell/CommandTest.php
tests/phpunit/includes/specialpage/ChangesListSpecialPageTest.php
tests/phpunit/includes/user/UserGroupMembershipTest.php
tests/phpunit/includes/user/UserTest.php
tests/phpunit/includes/watcheditem/WatchedItemQueryServiceUnitTest.php
tests/qunit/QUnitTestResources.php
tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js [deleted file]
tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js [new file with mode: 0644]

index ed1c4df..f79747a 100644 (file)
@@ -51,6 +51,18 @@ production.
 * Style tags with a 'data-mw-deduplicate' attribute will be deduplicated as a
   ParserOutput::getText() post-cache transformation. This may be disabled by
   passing 'deduplicateStyles' => false to that method.
+* The identity of the logged-in or IP "actor" for logged actions is being moved
+  into a new actor table, with the rows in tables such as revision and logging
+  referring to the actor ID instead of storing the user ID and name/IP in
+  every row.
+  * This is currently gated by $wgActorTableSchemaMigrationStage. Most wikis
+    can set this to MIGRATION_NEW and run maintenance/migrateActors.php as
+    soon as any necessary extensions are updated.
+  * Most code accessing rows for logged actions from the database should use
+    the relevant getQueryInfo() methods to get the information needed to build
+    the SQL query. The ActorMigration class may also be used to get feature-flagged
+    information needed to access actor-related fields during the migration
+    period.
 
 === External library changes in 1.31 ===
 
index 7f90d47..cff05b8 100644 (file)
@@ -11,6 +11,7 @@ $wgAutoloadLocalClasses = [
        'Action' => __DIR__ . '/includes/actions/Action.php',
        'ActiveUsersPager' => __DIR__ . '/includes/specials/pagers/ActiveUsersPager.php',
        'ActivityUpdateJob' => __DIR__ . '/includes/jobqueue/jobs/ActivityUpdateJob.php',
+       'ActorMigration' => __DIR__ . '/includes/ActorMigration.php',
        'AddRFCAndPMIDInterwiki' => __DIR__ . '/maintenance/addRFCandPMIDInterwiki.php',
        'AddSite' => __DIR__ . '/maintenance/addSite.php',
        'AjaxDispatcher' => __DIR__ . '/includes/AjaxDispatcher.php',
@@ -220,6 +221,7 @@ $wgAutoloadLocalClasses = [
        'CachedAction' => __DIR__ . '/includes/actions/CachedAction.php',
        'CachedBagOStuff' => __DIR__ . '/includes/libs/objectcache/CachedBagOStuff.php',
        'CachingSiteStore' => __DIR__ . '/includes/site/CachingSiteStore.php',
+       'CannotCreateActorException' => __DIR__ . '/includes/exception/CannotCreateActorException.php',
        'CapsCleanup' => __DIR__ . '/maintenance/cleanupCaps.php',
        'CategoriesRdf' => __DIR__ . '/includes/CategoriesRdf.php',
        'Category' => __DIR__ . '/includes/Category.php',
@@ -1020,6 +1022,7 @@ $wgAutoloadLocalClasses = [
        'MessageContent' => __DIR__ . '/includes/content/MessageContent.php',
        'MessageLocalizer' => __DIR__ . '/languages/MessageLocalizer.php',
        'MessageSpecifier' => __DIR__ . '/includes/libs/MessageSpecifier.php',
+       'MigrateActors' => __DIR__ . '/maintenance/migrateActors.php',
        'MigrateArchiveText' => __DIR__ . '/maintenance/migrateArchiveText.php',
        'MigrateComments' => __DIR__ . '/maintenance/migrateComments.php',
        'MigrateFileRepoLayout' => __DIR__ . '/maintenance/migrateFileRepoLayout.php',
diff --git a/includes/ActorMigration.php b/includes/ActorMigration.php
new file mode 100644 (file)
index 0000000..161c7a9
--- /dev/null
@@ -0,0 +1,383 @@
+<?php
+/**
+ * Methods to help with the actor table migration
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+use MediaWiki\MediaWikiServices;
+use MediaWiki\User\UserIdentity;
+use Wikimedia\Rdbms\IDatabase;
+
+/**
+ * This class handles the logic for the actor table migration.
+ *
+ * This is not intended to be a long-term part of MediaWiki; it will be
+ * deprecated and removed along with $wgActorTableSchemaMigrationStage.
+ *
+ * @since 1.31
+ */
+class ActorMigration {
+
+       /**
+        * Define fields that use temporary tables for transitional purposes
+        * @var array Keys are '$key', values are arrays with four fields:
+        *  - table: Temporary table name
+        *  - pk: Temporary table column referring to the main table's primary key
+        *  - field: Temporary table column referring actor.actor_id
+        *  - joinPK: Main table's primary key
+        */
+       private static $tempTables = [
+               'rev_user' => [
+                       'table' => 'revision_actor_temp',
+                       'pk' => 'revactor_rev',
+                       'field' => 'revactor_actor',
+                       'joinPK' => 'rev_id',
+                       'extra' => [
+                               'revactor_timestamp' => 'rev_timestamp',
+                               'revactor_page' => 'rev_page',
+                       ],
+               ],
+       ];
+
+       /**
+        * Fields that formerly used $tempTables
+        * @var array Key is '$key', value is the MediaWiki version in which it was
+        *  removed from $tempTables.
+        */
+       private static $formerTempTables = [];
+
+       /**
+        * Define fields that use non-standard mapping
+        * @var array Keys are the user id column name, values are arrays with two
+        *  elements (the user text column name and the actor id column name)
+        */
+       private static $specialFields = [
+               'ipb_by' => [ 'ipb_by_text', 'ipb_by_actor' ],
+       ];
+
+       /** @var array|null Cache for `self::getJoin()` */
+       private $joinCache = null;
+
+       /** @var int One of the MIGRATION_* constants */
+       private $stage;
+
+       /** @private */
+       public function __construct( $stage ) {
+               $this->stage = $stage;
+       }
+
+       /**
+        * Static constructor
+        * @return ActorMigration
+        */
+       public static function newMigration() {
+               return MediaWikiServices::getInstance()->getActorMigration();
+       }
+
+       /**
+        * Return an SQL condition to test if a user field is anonymous
+        * @param string $field Field name or SQL fragment
+        * @return string
+        */
+       public function isAnon( $field ) {
+               return $this->stage === MIGRATION_NEW ? "$field IS NULL" : "$field = 0";
+       }
+
+       /**
+        * Return an SQL condition to test if a user field is non-anonymous
+        * @param string $field Field name or SQL fragment
+        * @return string
+        */
+       public function isNotAnon( $field ) {
+               return $this->stage === MIGRATION_NEW ? "$field IS NOT NULL" : "$field != 0";
+       }
+
+       /**
+        * @param string $key A key such as "rev_user" identifying the actor
+        *  field being fetched.
+        * @return string[] [ $text, $actor ]
+        */
+       private static function getFieldNames( $key ) {
+               if ( isset( self::$specialFields[$key] ) ) {
+                       return self::$specialFields[$key];
+               }
+
+               return [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ];
+       }
+
+       /**
+        * Get SELECT fields and joins for the actor key
+        *
+        * @param string $key A key such as "rev_user" identifying the actor
+        *  field being fetched.
+        * @return array With three keys:
+        *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
+        *   - fields: (string[]) to include in the `$vars` to `IDatabase->select()`
+        *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
+        *  All tables, fields, and joins are aliased, so `+` is safe to use.
+        */
+       public function getJoin( $key ) {
+               if ( !isset( $this->joinCache[$key] ) ) {
+                       $tables = [];
+                       $fields = [];
+                       $joins = [];
+
+                       list( $text, $actor ) = self::getFieldNames( $key );
+
+                       if ( $this->stage === MIGRATION_OLD ) {
+                               $fields[$key] = $key;
+                               $fields[$text] = $text;
+                               $fields[$actor] = 'NULL';
+                       } else {
+                               $join = $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN';
+
+                               if ( isset( self::$tempTables[$key] ) ) {
+                                       $t = self::$tempTables[$key];
+                                       $alias = "temp_$key";
+                                       $tables[$alias] = $t['table'];
+                                       $joins[$alias] = [ $join, "{$alias}.{$t['pk']} = {$t['joinPK']}" ];
+                                       $joinField = "{$alias}.{$t['field']}";
+                               } else {
+                                       $joinField = $actor;
+                               }
+
+                               $alias = "actor_$key";
+                               $tables[$alias] = 'actor';
+                               $joins[$alias] = [ $join, "{$alias}.actor_id = {$joinField}" ];
+
+                               if ( $this->stage === MIGRATION_NEW ) {
+                                       $fields[$key] = "{$alias}.actor_user";
+                                       $fields[$text] = "{$alias}.actor_name";
+                               } else {
+                                       $fields[$key] = "COALESCE( {$alias}.actor_user, $key )";
+                                       $fields[$text] = "COALESCE( {$alias}.actor_name, $text )";
+                               }
+                               $fields[$actor] = $joinField;
+                       }
+
+                       $this->joinCache[$key] = [
+                               'tables' => $tables,
+                               'fields' => $fields,
+                               'joins' => $joins,
+                       ];
+               }
+
+               return $this->joinCache[$key];
+       }
+
+       /**
+        * Get UPDATE fields for the actor
+        *
+        * @param IDatabase $dbw Database to use for creating an actor ID, if necessary
+        * @param string $key A key such as "rev_user" identifying the actor
+        *  field being fetched.
+        * @param UserIdentity $user User to set in the update
+        * @return array to merge into `$values` to `IDatabase->update()` or `$a` to `IDatabase->insert()`
+        */
+       public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
+               if ( isset( self::$tempTables[$key] ) ) {
+                       throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
+               }
+
+               list( $text, $actor ) = self::getFieldNames( $key );
+               $ret = [];
+               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+                       $ret[$key] = $user->getId();
+                       $ret[$text] = $user->getName();
+               }
+               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+                       // We need to be able to assign an actor ID if none exists
+                       if ( !$user instanceof User && !$user->getActorId() ) {
+                               $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
+                       }
+                       $ret[$actor] = $user->getActorId( $dbw );
+               }
+               return $ret;
+       }
+
+       /**
+        * Get UPDATE fields for the actor
+        *
+        * @param IDatabase $dbw Database to use for creating an actor ID, if necessary
+        * @param string $key A key such as "rev_user" identifying the actor
+        *  field being fetched.
+        * @param UserIdentity $user User to set in the update
+        * @return array with two values:
+        *  - array to merge into `$values` to `IDatabase->update()` or `$a` to `IDatabase->insert()`
+        *  - callback to call with the the primary key for the main table insert
+        *    and extra fields needed for the temp table.
+        */
+       public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
+               if ( isset( self::$formerTempTables[$key] ) ) {
+                       wfDeprecated( __METHOD__ . " for $key", self::$formerTempTables[$key] );
+               } elseif ( !isset( self::$tempTables[$key] ) ) {
+                       throw new InvalidArgumentException( "Must use getInsertValues() for $key" );
+               }
+
+               list( $text, $actor ) = self::getFieldNames( $key );
+               $ret = [];
+               $callback = null;
+               if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
+                       $ret[$key] = $user->getId();
+                       $ret[$text] = $user->getName();
+               }
+               if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
+                       // We need to be able to assign an actor ID if none exists
+                       if ( !$user instanceof User && !$user->getActorId() ) {
+                               $user = User::newFromAnyId( $user->getId(), $user->getName(), null );
+                       }
+                       $id = $user->getActorId( $dbw );
+
+                       if ( isset( self::$tempTables[$key] ) ) {
+                               $func = __METHOD__;
+                               $callback = function ( $pk, array $extra ) use ( $dbw, $key, $id, $func ) {
+                                       $t = self::$tempTables[$key];
+                                       $set = [ $t['field'] => $id ];
+                                       foreach ( $t['extra'] as $to => $from ) {
+                                               if ( !array_key_exists( $from, $extra ) ) {
+                                                       throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
+                                               }
+                                               $set[$to] = $extra[$from];
+                                       }
+                                       $dbw->upsert(
+                                               $t['table'],
+                                               [ $t['pk'] => $pk ] + $set,
+                                               [ $t['pk'] ],
+                                               $set,
+                                               $func
+                                       );
+                               };
+                       } else {
+                               $ret[$actor] = $id;
+                               $callback = function ( $pk, array $extra ) {
+                               };
+                       }
+               } elseif ( isset( self::$tempTables[$key] ) ) {
+                       $func = __METHOD__;
+                       $callback = function ( $pk, array $extra ) use ( $key, $func ) {
+                               $t = self::$tempTables[$key];
+                               foreach ( $t['extra'] as $to => $from ) {
+                                       if ( !array_key_exists( $from, $extra ) ) {
+                                               throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
+                                       }
+                               }
+                       };
+               } else {
+                       $callback = function ( $pk, array $extra ) {
+                       };
+               }
+               return [ $ret, $callback ];
+       }
+
+       /**
+        * Get WHERE condition for the actor
+        *
+        * @param IDatabase $db Database to use for quoting and list-making
+        * @param string $key A key such as "rev_user" identifying the actor
+        *  field being fetched.
+        * @param UserIdentity|UserIdentity[] $users Users to test for
+        * @param bool $useId If false, don't try to query by the user ID.
+        *  Intended for use with rc_user since it has an index on
+        *  (rc_user_text,rc_timestamp) but not (rc_user,rc_timestamp).
+        * @return array With three keys:
+        *   - tables: (string[]) to include in the `$table` to `IDatabase->select()`
+        *   - conds: (string) to include in the `$cond` to `IDatabase->select()`
+        *   - orconds: (array[]) array of alternatives in case a union of multiple
+        *     queries would be more efficient than a query with OR. May have keys
+        *     'actor', 'userid', 'username'.
+        *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
+        *  All tables and joins are aliased, so `+` is safe to use.
+        */
+       public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
+               $tables = [];
+               $conds = [];
+               $joins = [];
+
+               if ( $users instanceof UserIdentity ) {
+                       $users = [ $users ];
+               }
+
+               // Get information about all the passed users
+               $ids = [];
+               $names = [];
+               $actors = [];
+               foreach ( $users as $user ) {
+                       if ( $useId && $user->getId() ) {
+                               $ids[] = $user->getId();
+                       } else {
+                               $names[] = $user->getName();
+                       }
+                       $actorId = $user->getActorId();
+                       if ( $actorId ) {
+                               $actors[] = $actorId;
+                       }
+               }
+
+               list( $text, $actor ) = self::getFieldNames( $key );
+
+               // Combine data into conditions to be ORed together
+               $actorNotEmpty = [];
+               if ( $this->stage === MIGRATION_OLD ) {
+                       $actors = [];
+                       $actorEmpty = [];
+               } elseif ( isset( self::$tempTables[$key] ) ) {
+                       $t = self::$tempTables[$key];
+                       $alias = "temp_$key";
+                       $tables[$alias] = $t['table'];
+                       $joins[$alias] = [
+                               $this->stage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                               "{$alias}.{$t['pk']} = {$t['joinPK']}"
+                       ];
+                       $joinField = "{$alias}.{$t['field']}";
+                       $actorEmpty = [ $joinField => null ];
+                       if ( $this->stage !== MIGRATION_NEW ) {
+                               // Otherwise the resulting test can evaluate to NULL, and
+                               // NOT(NULL) is NULL rather than true.
+                               $actorNotEmpty = [ "$joinField IS NOT NULL" ];
+                       }
+               } else {
+                       $joinField = $actor;
+                       $actorEmpty = [ $joinField => 0 ];
+               }
+
+               if ( $actors ) {
+                       $conds['actor'] = $db->makeList(
+                               $actorNotEmpty + [ $joinField => $actors ], IDatabase::LIST_AND
+                       );
+               }
+               if ( $this->stage < MIGRATION_NEW && $ids ) {
+                       $conds['userid'] = $db->makeList(
+                               $actorEmpty + [ $key => $ids ], IDatabase::LIST_AND
+                       );
+               }
+               if ( $this->stage < MIGRATION_NEW && $names ) {
+                       $conds['username'] = $db->makeList(
+                               $actorEmpty + [ $text => $names ], IDatabase::LIST_AND
+                       );
+               }
+
+               return [
+                       'tables' => $tables,
+                       'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
+                       'orconds' => $conds,
+                       'joins' => $joins,
+               ];
+       }
+
+}
index bdc6702..e23a8ff 100644 (file)
@@ -206,12 +206,25 @@ class Block {
         * @return array
         */
        public static function selectFields() {
+               global $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->ipb_by or $row->ipb_by_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
+
                wfDeprecated( __METHOD__, '1.31' );
                return [
                        'ipb_id',
                        'ipb_address',
                        'ipb_by',
                        'ipb_by_text',
+                       'ipb_by_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'ipb_by_actor' : null,
                        'ipb_timestamp',
                        'ipb_auto',
                        'ipb_anon_only',
@@ -236,13 +249,12 @@ class Block {
         */
        public static function getQueryInfo() {
                $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
                return [
-                       'tables' => [ 'ipblocks' ] + $commentQuery['tables'],
+                       'tables' => [ 'ipblocks' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                'ipb_id',
                                'ipb_address',
-                               'ipb_by',
-                               'ipb_by_text',
                                'ipb_timestamp',
                                'ipb_auto',
                                'ipb_anon_only',
@@ -253,8 +265,8 @@ class Block {
                                'ipb_block_email',
                                'ipb_allow_usertalk',
                                'ipb_parent_block_id',
-                       ] + $commentQuery['fields'],
-                       'joins' => $commentQuery['joins'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       'joins' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
        }
 
@@ -445,11 +457,9 @@ class Block {
         */
        protected function initFromRow( $row ) {
                $this->setTarget( $row->ipb_address );
-               if ( $row->ipb_by ) { // local user
-                       $this->setBlocker( User::newFromId( $row->ipb_by ) );
-               } else { // foreign user
-                       $this->setBlocker( $row->ipb_by_text );
-               }
+               $this->setBlocker( User::newFromAnyId(
+                       $row->ipb_by, $row->ipb_by_text, isset( $row->ipb_by_actor ) ? $row->ipb_by_actor : null
+               ) );
 
                $this->mTimestamp = wfTimestamp( TS_MW, $row->ipb_timestamp );
                $this->mAuto = $row->ipb_auto;
@@ -519,6 +529,9 @@ class Block {
                if ( $this->getSystemBlockType() !== null ) {
                        throw new MWException( 'Cannot insert a system block into the database' );
                }
+               if ( !$this->getBlocker() || $this->getBlocker()->getName() === '' ) {
+                       throw new MWException( 'Cannot insert a block without a blocker set' );
+               }
 
                wfDebug( "Block::insert; timestamp {$this->mTimestamp}\n" );
 
@@ -640,8 +653,6 @@ class Block {
                $a = [
                        'ipb_address'          => (string)$this->target,
                        'ipb_user'             => $uid,
-                       'ipb_by'               => $this->getBy(),
-                       'ipb_by_text'          => $this->getByName(),
                        'ipb_timestamp'        => $dbw->timestamp( $this->mTimestamp ),
                        'ipb_auto'             => $this->mAuto,
                        'ipb_anon_only'        => !$this->isHardblock(),
@@ -654,7 +665,8 @@ class Block {
                        'ipb_block_email'      => $this->prevents( 'sendemail' ),
                        'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
                        'ipb_parent_block_id'  => $this->mParentBlockId
-               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason );
+               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
+                       + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
 
                return $a;
        }
@@ -665,12 +677,11 @@ class Block {
         */
        protected function getAutoblockUpdateArray( IDatabase $dbw ) {
                return [
-                       'ipb_by'               => $this->getBy(),
-                       'ipb_by_text'          => $this->getByName(),
                        'ipb_create_account'   => $this->prevents( 'createaccount' ),
                        'ipb_deleted'          => (int)$this->mHideName, // typecast required for SQLite
                        'ipb_allow_usertalk'   => !$this->prevents( 'editownusertalk' ),
-               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason );
+               ] + CommentStore::getStore()->insert( $dbw, 'ipb_reason', $this->mReason )
+                       + ActorMigration::newMigration()->getInsertValues( $dbw, 'ipb_by', $this->getBlocker() );
        }
 
        /**
@@ -710,16 +721,27 @@ class Block {
                        return;
                }
 
+               $target = $block->getTarget();
+               if ( is_string( $target ) ) {
+                       $target = User::newFromName( $target, false );
+               }
+
                $dbr = wfGetDB( DB_REPLICA );
+               $rcQuery = ActorMigration::newMigration()->getWhere( $dbr, 'rc_user', $target, false );
 
                $options = [ 'ORDER BY' => 'rc_timestamp DESC' ];
-               $conds = [ 'rc_user_text' => (string)$block->getTarget() ];
 
                // Just the last IP used.
                $options['LIMIT'] = 1;
 
-               $res = $dbr->select( 'recentchanges', [ 'rc_ip' ], $conds,
-                       __METHOD__, $options );
+               $res = $dbr->select(
+                       [ 'recentchanges' ] + $rcQuery['tables'],
+                       [ 'rc_ip' ],
+                       $rcQuery['conds'],
+                       __METHOD__,
+                       $options,
+                       $rcQuery['joins']
+               );
 
                if ( !$res->numRows() ) {
                        # No results, don't autoblock anything
@@ -1471,7 +1493,7 @@ class Block {
 
        /**
         * Get the user who implemented this block
-        * @return User|string Local User object or string for a foreign user
+        * @return User User object. May name a foreign user.
         */
        public function getBlocker() {
                return $this->blocker;
index a54f4cf..33418d8 100644 (file)
@@ -8830,6 +8830,13 @@ $wgInterwikiPrefixDisplayTypes = [];
  */
 $wgCommentTableSchemaMigrationStage = MIGRATION_OLD;
 
+/**
+ * Actor table schema migration stage.
+ * @since 1.31
+ * @var int One of the MIGRATION_* constants
+ */
+$wgActorTableSchemaMigrationStage = MIGRATION_OLD;
+
 /**
  * For really cool vim folding this needs to be at the end:
  * vim: foldmarker=@{,@} foldmethod=marker
index 652b355..08c4a72 100644 (file)
@@ -3138,11 +3138,15 @@ ERROR;
         * @return array
         */
        private function getSummaryInputAttributes( array $inputAttrs = null ) {
-               // Note: the maxlength is overridden in JS to 255 and to make it use UTF-8 bytes, not characters.
+               $conf = $this->context->getConfig();
+               $oldCommentSchema = $conf->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD;
+               // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
+               // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
+               // Unicode codepoints (or 255 UTF-8 bytes for old schema).
                return ( is_array( $inputAttrs ) ? $inputAttrs : [] ) + [
                        'id' => 'wpSummary',
                        'name' => 'wpSummary',
-                       'maxlength' => '200',
+                       'maxlength' => $oldCommentSchema ? 200 : CommentStore::COMMENT_CHARACTER_LIMIT,
                        'tabindex' => 1,
                        'size' => 60,
                        'spellcheck' => 'true',
@@ -3785,30 +3789,30 @@ ERROR;
        protected function getLastDelete() {
                $dbr = wfGetDB( DB_REPLICA );
                $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
                $data = $dbr->selectRow(
-                       [ 'logging', 'user' ] + $commentQuery['tables'],
+                       array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
                        [
                                'log_type',
                                'log_action',
                                'log_timestamp',
-                               'log_user',
                                'log_namespace',
                                'log_title',
                                'log_params',
                                'log_deleted',
                                'user_name'
-                       ] + $commentQuery['fields'], [
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       [
                                'log_namespace' => $this->mTitle->getNamespace(),
                                'log_title' => $this->mTitle->getDBkey(),
                                'log_type' => 'delete',
                                'log_action' => 'delete',
-                               'user_id=log_user'
                        ],
                        __METHOD__,
                        [ 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ],
                        [
-                               'user' => [ 'JOIN', 'user_id=log_user' ],
-                       ] + $commentQuery['joins']
+                               'user' => [ 'JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ],
+                       ] + $commentQuery['joins'] + $actorQuery['joins']
                );
                // Quick paranoid permission checks...
                if ( is_object( $data ) ) {
index fb446b4..5fc5eb1 100644 (file)
@@ -1752,9 +1752,10 @@ class Linker {
                $dbr = wfGetDB( DB_REPLICA );
 
                // Up to the value of $wgShowRollbackEditCount revisions are counted
+               $revQuery = Revision::getQueryInfo();
                $res = $dbr->select(
-                       'revision',
-                       [ 'rev_user_text', 'rev_deleted' ],
+                       $revQuery['tables'],
+                       [ 'rev_user_text' => $revQuery['fields']['rev_user_text'], 'rev_deleted' ],
                        // $rev->getPage() returns null sometimes
                        [ 'rev_page' => $rev->getTitle()->getArticleID() ],
                        __METHOD__,
@@ -1762,7 +1763,8 @@ class Linker {
                                'USE INDEX' => [ 'revision' => 'page_timestamp' ],
                                'ORDER BY' => 'rev_timestamp DESC',
                                'LIMIT' => $wgShowRollbackEditCount + 1
-                       ]
+                       ],
+                       $revQuery['joins']
                );
 
                $editCount = 0;
index 59f194d..3c8ce65 100644 (file)
@@ -794,6 +794,14 @@ class MediaWikiServices extends ServiceContainer {
                return $this->getService( 'CommentStore' );
        }
 
+       /**
+        * @since 1.31
+        * @return ActorMigration
+        */
+       public function getActorMigration() {
+               return $this->getService( 'ActorMigration' );
+       }
+
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service getter here, don't forget to add a test
        // case for it in MediaWikiServicesTest::provideGetters() and in
index 53608e8..51c2923 100644 (file)
@@ -349,7 +349,9 @@ class ProtectionForm {
                $user = $context->getUser();
                $output = $context->getOutput();
                $lang = $context->getLanguage();
-               $cascadingRestrictionLevels = $context->getConfig()->get( 'CascadingRestrictionLevels' );
+               $conf = $context->getConfig();
+               $cascadingRestrictionLevels = $conf->get( 'CascadingRestrictionLevels' );
+               $oldCommentSchema = $conf->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD;
                $out = '';
                if ( !$this->disabled ) {
                        $output->addModules( 'mediawiki.legacy.protect' );
@@ -494,6 +496,13 @@ class ProtectionForm {
                                $this->mReasonSelection,
                                'mwProtect-reason', 4 );
 
+                       // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
+                       // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
+                       // Unicode codepoints (or 180 UTF-8 bytes for old schema).
+                       // Subtract arbitrary 75 to leave some space for the autogenerated null edit's summary
+                       // and other texts chosen by dropdown menus on this page.
+                       $maxlength = $oldCommentSchema ? 180 : CommentStore::COMMENT_CHARACTER_LIMIT - 75;
+
                        $out .= Xml::openElement( 'table', [ 'id' => 'mw-protect-table3' ] ) .
                                Xml::openElement( 'tbody' );
                        $out .= "
@@ -511,10 +520,7 @@ class ProtectionForm {
                                        </td>
                                        <td class='mw-input'>" .
                                                Xml::input( 'mwProtect-reason', 60, $this->mReason, [ 'type' => 'text',
-                                                       'id' => 'mwProtect-reason', 'maxlength' => 180 ] ) .
-                                                       // Limited maxlength as the database trims at 255 bytes and other texts
-                                                       // chosen by dropdown menus on this page are also included in this database field.
-                                                       // The byte limit of 180 bytes is enforced in javascript
+                                                       'id' => 'mwProtect-reason', 'maxlength' => $maxlength ] ) .
                                        "</td>
                                </tr>";
                        # Disallow watching is user is not logged in
index d9d3149..f8a3fcc 100644 (file)
@@ -29,7 +29,6 @@ use MediaWiki\Storage\RevisionStore;
 use MediaWiki\Storage\RevisionStoreRecord;
 use MediaWiki\Storage\SlotRecord;
 use MediaWiki\Storage\SqlBlobStore;
-use MediaWiki\User\UserIdentityValue;
 use Wikimedia\Rdbms\IDatabase;
 use MediaWiki\Linker\LinkTarget;
 use MediaWiki\MediaWikiServices;
@@ -316,7 +315,18 @@ class Revision implements IDBAccessObject {
         * @return array
         */
        public static function userJoinCond() {
+               global $wgActorTableSchemaMigrationStage;
+
                wfDeprecated( __METHOD__, '1.31' );
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's
+                       // no way the join it's trying to do can work once the old fields
+                       // aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
+
                return [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
        }
 
@@ -339,7 +349,17 @@ class Revision implements IDBAccessObject {
         * @return array
         */
        public static function selectFields() {
-               global $wgContentHandlerUseDB;
+               global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->rev_user or $row->rev_user_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
 
                wfDeprecated( __METHOD__, '1.31' );
 
@@ -350,6 +370,7 @@ class Revision implements IDBAccessObject {
                        'rev_timestamp',
                        'rev_user_text',
                        'rev_user',
+                       'rev_actor' => 'NULL',
                        'rev_minor_edit',
                        'rev_deleted',
                        'rev_len',
@@ -374,7 +395,17 @@ class Revision implements IDBAccessObject {
         * @return array
         */
        public static function selectArchiveFields() {
-               global $wgContentHandlerUseDB;
+               global $wgContentHandlerUseDB, $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->ar_user or $row->ar_user_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
 
                wfDeprecated( __METHOD__, '1.31' );
 
@@ -387,6 +418,7 @@ class Revision implements IDBAccessObject {
                        'ar_timestamp',
                        'ar_user_text',
                        'ar_user',
+                       'ar_actor' => 'NULL',
                        'ar_minor_edit',
                        'ar_deleted',
                        'ar_len',
@@ -623,7 +655,7 @@ class Revision implements IDBAccessObject {
         */
        public function setUserIdAndName( $id, $name ) {
                if ( $this->mRecord instanceof MutableRevisionRecord ) {
-                       $user = new UserIdentityValue( intval( $id ), $name );
+                       $user = User::newFromAnyId( intval( $id ), $name, null );
                        $this->mRecord->setUser( $user );
                } else {
                        throw new MWException( __METHOD__ . ' is not supported on this instance' );
index fa454e0..5243cc6 100644 (file)
@@ -203,6 +203,16 @@ abstract class RevisionItemBase {
                return false;
        }
 
+       /**
+        * Get the DB field name storing actor ids.
+        * Override this function.
+        * @since 1.31
+        * @return bool
+        */
+       public function getAuthorActorField() {
+               return false;
+       }
+
        /**
         * Get the ID, as it would appear in the ids URL parameter
         * @return int
@@ -257,6 +267,16 @@ abstract class RevisionItemBase {
                return strval( $this->row->$field );
        }
 
+       /**
+        * Get the author actor ID
+        * @since 1.31
+        * @return string
+        */
+       public function getAuthorActor() {
+               $field = $this->getAuthorActorField();
+               return strval( $this->row->$field );
+       }
+
        /**
         * Returns true if the current user can view the item
         */
index dab9fb9..3e3c897 100644 (file)
@@ -179,7 +179,8 @@ return [
        'WatchedItemQueryService' => function ( MediaWikiServices $services ) {
                return new WatchedItemQueryService(
                        $services->getDBLoadBalancer(),
-                       $services->getCommentStore()
+                       $services->getCommentStore(),
+                       $services->getActorMigration()
                );
        },
 
@@ -500,7 +501,8 @@ return [
                        $services->getDBLoadBalancer(),
                        $blobStore,
                        $services->getMainWANObjectCache(),
-                       $services->getCommentStore()
+                       $services->getCommentStore(),
+                       $services->getActorMigration()
                );
 
                $store->setLogger( LoggerFactory::getInstance( 'RevisionStore' ) );
@@ -555,7 +557,13 @@ return [
                        $wgContLang,
                        $services->getMainConfig()->get( 'CommentTableSchemaMigrationStage' )
                );
-       }
+       },
+
+       'ActorMigration' => function ( MediaWikiServices $services ) {
+               return new ActorMigration(
+                       $services->getMainConfig()->get( 'ActorTableSchemaMigrationStage' )
+               );
+       },
 
        ///////////////////////////////////////////////////////////////////////////
        // NOTE: When adding a service here, don't forget to add a getter function
index 28caf3a..8b1112b 100644 (file)
@@ -110,4 +110,10 @@ interface BlobStore {
         */
        public function storeBlob( $data, $hints = [] );
 
+       /**
+        * Check if the blob metadata or backing blob data store is read-only
+        *
+        * @return bool
+        */
+       public function isReadOnly();
 }
index 2992211..e13fc1f 100644 (file)
@@ -26,6 +26,7 @@
 
 namespace MediaWiki\Storage;
 
+use ActorMigration;
 use CommentStore;
 use CommentStoreComment;
 use Content;
@@ -97,6 +98,11 @@ class RevisionStore
         */
        private $commentStore;
 
+       /**
+        * @var ActorMigration
+        */
+       private $actorMigration;
+
        /**
         * @var LoggerInterface
         */
@@ -109,6 +115,7 @@ class RevisionStore
         * @param SqlBlobStore $blobStore
         * @param WANObjectCache $cache
         * @param CommentStore $commentStore
+        * @param ActorMigration $actorMigration
         * @param bool|string $wikiId
         */
        public function __construct(
@@ -116,6 +123,7 @@ class RevisionStore
                SqlBlobStore $blobStore,
                WANObjectCache $cache,
                CommentStore $commentStore,
+               ActorMigration $actorMigration,
                $wikiId = false
        ) {
                Assert::parameterType( 'string|boolean', $wikiId, '$wikiId' );
@@ -124,6 +132,7 @@ class RevisionStore
                $this->blobStore = $blobStore;
                $this->cache = $cache;
                $this->commentStore = $commentStore;
+               $this->actorMigration = $actorMigration;
                $this->wikiId = $wikiId;
                $this->logger = new NullLogger();
        }
@@ -132,6 +141,13 @@ class RevisionStore
                $this->logger = $logger;
        }
 
+       /**
+        * @return bool Whether the store is read-only
+        */
+       public function isReadOnly() {
+               return $this->blobStore->isReadOnly();
+       }
+
        /**
         * @return bool
         */
@@ -381,14 +397,16 @@ class RevisionStore
                $user = $this->failOnNull( $rev->getUser( RevisionRecord::RAW ), 'user' );
                $timestamp = $this->failOnEmpty( $rev->getTimestamp(), 'timestamp field' );
 
+               // Checks.
+               $this->failOnNull( $user->getId(), 'user field' );
+               $this->failOnEmpty( $user->getName(), 'user_text field' );
+
                # Record the edit in revisions
                $row = [
                        'rev_page'       => $pageId,
                        'rev_parent_id'  => $parentId,
                        'rev_text_id'    => $textId,
                        'rev_minor_edit' => $rev->isMinor() ? 1 : 0,
-                       'rev_user'       => $this->failOnNull( $user->getId(), 'user field' ),
-                       'rev_user_text'  => $this->failOnEmpty( $user->getName(), 'user_text field' ),
                        'rev_timestamp'  => $dbw->timestamp( $timestamp ),
                        'rev_deleted'    => $rev->getVisibility(),
                        'rev_len'        => $size,
@@ -404,6 +422,10 @@ class RevisionStore
                        $this->commentStore->insertWithTempTable( $dbw, 'rev_comment', $comment );
                $row += $commentFields;
 
+               list( $actorFields, $actorCallback ) =
+                       $this->actorMigration->getInsertValuesWithTempTable( $dbw, 'rev_user', $user );
+               $row += $actorFields;
+
                if ( $this->contentHandlerUseDB ) {
                        // MCR migration note: rev_content_model and rev_content_format will go away
 
@@ -421,13 +443,14 @@ class RevisionStore
                        $row['rev_id'] = intval( $dbw->insertId() );
                }
                $commentCallback( $row['rev_id'] );
+               $actorCallback( $row['rev_id'], $row );
 
                // Insert IP revision into ip_changes for use when querying for a range.
-               if ( $row['rev_user'] === 0 && IP::isValid( $row['rev_user_text'] ) ) {
+               if ( $user->getId() === 0 && IP::isValid( $user->getName() ) ) {
                        $ipcRow = [
                                'ipc_rev_id'        => $row['rev_id'],
                                'ipc_rev_timestamp' => $row['rev_timestamp'],
-                               'ipc_hex'           => IP::toHex( $row['rev_user_text'] ),
+                               'ipc_hex'           => IP::toHex( $user->getName() ),
                        ];
                        $dbw->insert( 'ip_changes', $ipcRow, __METHOD__ );
                }
@@ -435,8 +458,6 @@ class RevisionStore
                $newSlot = SlotRecord::newSaved( $row['rev_id'], $blobAddress, $slot );
                $slots = new RevisionSlots( [ 'main' => $newSlot ] );
 
-               $user = new UserIdentityValue( intval( $row['rev_user'] ), $row['rev_user_text'] );
-
                $rev = new RevisionStoreRecord(
                        $title,
                        $user,
@@ -576,6 +597,7 @@ class RevisionStore
                                'page'       => $title->getArticleID(),
                                'user_text'  => $user->getName(),
                                'user'       => $user->getId(),
+                               'actor'      => $user->getActorId(),
                                'comment'    => $comment,
                                'minor_edit' => $minor,
                                'text_id'    => $current->rev_text_id,
@@ -647,9 +669,10 @@ class RevisionStore
                }
 
                // TODO: Select by rc_this_oldid alone - but as of Nov 2017, there is no index on that!
+               $actorWhere = $this->actorMigration->getWhere( $dbr, 'rc_user', $rev->getUser(), false );
                $rc = RecentChange::newFromConds(
                        [
-                               'rc_user_text' => $userIdentity->getName(),
+                               $actorWhere['conds'],
                                'rc_timestamp' => $dbr->timestamp( $rev->getTimestamp() ),
                                'rc_this_oldid' => $rev->getId()
                        ],
@@ -684,6 +707,7 @@ class RevisionStore
                        'ar_timestamp'      => 'rev_timestamp',
                        'ar_user_text'      => 'rev_user_text',
                        'ar_user'           => 'rev_user',
+                       'ar_actor'          => 'rev_actor',
                        'ar_minor_edit'     => 'rev_minor_edit',
                        'ar_deleted'        => 'rev_deleted',
                        'ar_len'            => 'rev_len',
@@ -734,7 +758,7 @@ class RevisionStore
 
                if ( is_object( $row ) ) {
                        // archive row
-                       if ( !isset( $row->rev_id ) && isset( $row->ar_user ) ) {
+                       if ( !isset( $row->rev_id ) && ( isset( $row->ar_user ) || isset( $row->ar_actor ) ) ) {
                                $row = $this->mapArchiveFields( $row );
                        }
 
@@ -1073,7 +1097,16 @@ class RevisionStore
                        $row->$field = $value;
                }
 
-               $user = $this->getUserIdentityFromRowObject( $row, 'ar_' );
+               try {
+                       $user = User::newFromAnyId(
+                               isset( $row->ar_user ) ? $row->ar_user : null,
+                               isset( $row->ar_user_text ) ? $row->ar_user_text : null,
+                               isset( $row->ar_actor ) ? $row->ar_actor : null
+                       );
+               } catch ( InvalidArgumentException $ex ) {
+                       wfWarn( __METHOD__ . ': ' . $ex->getMessage() );
+                       $user = new UserIdentityValue( 0, '', 0 );
+               }
 
                $comment = $this->commentStore
                        // Legacy because $row may have come from self::selectFields()
@@ -1085,34 +1118,6 @@ class RevisionStore
                return new RevisionArchiveRecord( $title, $user, $comment, $row, $slots, $this->wikiId );
        }
 
-       /**
-        * @param object $row
-        * @param string $prefix Field prefix, such as 'rev_' or 'ar_'.
-        *
-        * @return UserIdentityValue
-        */
-       private function getUserIdentityFromRowObject( $row, $prefix = 'rev_' ) {
-               $idField = "{$prefix}user";
-               $nameField = "{$prefix}user_text";
-
-               $userId = intval( $row->$idField );
-
-               if ( isset( $row->user_name ) ) {
-                       $userName = $row->user_name;
-               } elseif ( isset( $row->$nameField ) ) {
-                       $userName = $row->$nameField;
-               } else {
-                       $userName = User::whoIs( $userId );
-               }
-
-               if ( $userName === false ) {
-                       wfWarn( __METHOD__ . ': Cannot determine user name for user ID ' . $userId );
-                       $userName = '';
-               }
-
-               return new UserIdentityValue( $userId, $userName );
-       }
-
        /**
         * @see RevisionFactory::newRevisionFromRow_1_29
         *
@@ -1143,7 +1148,16 @@ class RevisionStore
                        }
                }
 
-               $user = $this->getUserIdentityFromRowObject( $row );
+               try {
+                       $user = User::newFromAnyId(
+                               isset( $row->rev_user ) ? $row->rev_user : null,
+                               isset( $row->rev_user_text ) ? $row->rev_user_text : null,
+                               isset( $row->rev_actor ) ? $row->rev_actor : null
+                       );
+               } catch ( InvalidArgumentException $ex ) {
+                       wfWarn( __METHOD__ . ': ' . $ex->getMessage() );
+                       $user = new UserIdentityValue( 0, '', 0 );
+               }
 
                $comment = $this->commentStore
                        // Legacy because $row may have come from self::selectFields()
@@ -1222,27 +1236,6 @@ class RevisionStore
                        }
                }
 
-               // Replaces old lazy loading logic in Revision::getUserText.
-               if ( !isset( $fields['user_text'] ) && isset( $fields['user'] ) ) {
-                       if ( $fields['user'] instanceof UserIdentity ) {
-                               /** @var User $user */
-                               $user = $fields['user'];
-                               $fields['user_text'] = $user->getName();
-                               $fields['user'] = $user->getId();
-                       } else {
-                               // TODO: wrap this in a callback to make it lazy again.
-                               $name = $fields['user'] === 0 ? false : User::whoIs( $fields['user'] );
-
-                               if ( $name === false ) {
-                                       throw new MWException(
-                                               'user_text not given, and unknown user ID ' . $fields['user']
-                                       );
-                               }
-
-                               $fields['user_text'] = $name;
-                       }
-               }
-
                if (
                        isset( $fields['comment'] )
                        && !( $fields['comment'] instanceof CommentStoreComment )
@@ -1285,16 +1278,15 @@ class RevisionStore
 
                if ( isset( $fields['user'] ) && ( $fields['user'] instanceof UserIdentity ) ) {
                        $user = $fields['user'];
-               } elseif ( isset( $fields['user'] ) && isset( $fields['user_text'] ) ) {
-                       $user = new UserIdentityValue( intval( $fields['user'] ), $fields['user_text'] );
-               } elseif ( isset( $fields['user'] ) ) {
-                       $user = User::newFromId( intval( $fields['user'] ) );
-               } elseif ( isset( $fields['user_text'] ) ) {
-                       $user = User::newFromName( $fields['user_text'] );
-
-                       // User::newFromName will return false for IP addresses (and invalid names)
-                       if ( $user == false ) {
-                               $user = new UserIdentityValue( 0, $fields['user_text'] );
+               } else {
+                       try {
+                               $user = User::newFromAnyId(
+                                       isset( $fields['user'] ) ? $fields['user'] : null,
+                                       isset( $fields['user_text'] ) ? $fields['user_text'] : null,
+                                       isset( $fields['actor'] ) ? $fields['actor'] : null
+                               );
+                       } catch ( InvalidArgumentException $ex ) {
+                               $user = null;
                        }
                }
 
@@ -1611,8 +1603,6 @@ class RevisionStore
                        'rev_page',
                        'rev_text_id',
                        'rev_timestamp',
-                       'rev_user_text',
-                       'rev_user',
                        'rev_minor_edit',
                        'rev_deleted',
                        'rev_len',
@@ -1625,6 +1615,11 @@ class RevisionStore
                $ret['fields'] = array_merge( $ret['fields'], $commentQuery['fields'] );
                $ret['joins'] = array_merge( $ret['joins'], $commentQuery['joins'] );
 
+               $actorQuery = $this->actorMigration->getJoin( 'rev_user' );
+               $ret['tables'] = array_merge( $ret['tables'], $actorQuery['tables'] );
+               $ret['fields'] = array_merge( $ret['fields'], $actorQuery['fields'] );
+               $ret['joins'] = array_merge( $ret['joins'], $actorQuery['joins'] );
+
                if ( $this->contentHandlerUseDB ) {
                        $ret['fields'][] = 'rev_content_format';
                        $ret['fields'][] = 'rev_content_model';
@@ -1648,7 +1643,8 @@ class RevisionStore
                        $ret['fields'] = array_merge( $ret['fields'], [
                                'user_name',
                        ] );
-                       $ret['joins']['user'] = [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ];
+                       $u = $actorQuery['fields']['rev_user'];
+                       $ret['joins']['user'] = [ 'LEFT JOIN', [ "$u != 0", "user_id = $u" ] ];
                }
 
                if ( in_array( 'text', $options, true ) ) {
@@ -1678,8 +1674,9 @@ class RevisionStore
         */
        public function getArchiveQueryInfo() {
                $commentQuery = $this->commentStore->getJoin( 'ar_comment' );
+               $actorQuery = $this->actorMigration->getJoin( 'ar_user' );
                $ret = [
-                       'tables' => [ 'archive' ] + $commentQuery['tables'],
+                       'tables' => [ 'archive' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                        'ar_id',
                                        'ar_page_id',
@@ -1689,15 +1686,13 @@ class RevisionStore
                                        'ar_text',
                                        'ar_text_id',
                                        'ar_timestamp',
-                                       'ar_user_text',
-                                       'ar_user',
                                        'ar_minor_edit',
                                        'ar_deleted',
                                        'ar_len',
                                        'ar_parent_id',
                                        'ar_sha1',
-                               ] + $commentQuery['fields'],
-                       'joins' => $commentQuery['joins'],
+                               ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       'joins' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
 
                if ( $this->contentHandlerUseDB ) {
@@ -1920,15 +1915,19 @@ class RevisionStore
                        return false;
                }
 
+               $revQuery = self::getQueryInfo();
                $res = $db->select(
-                       'revision',
-                       'rev_user',
+                       $revQuery['tables'],
+                       [
+                               'rev_user' => $revQuery['fields']['rev_user'],
+                       ],
                        [
                                'rev_page' => $pageId,
                                'rev_timestamp > ' . $db->addQuotes( $db->timestamp( $since ) )
                        ],
                        __METHOD__,
-                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ]
+                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
+                       $revQuery['joins']
                );
                foreach ( $res as $row ) {
                        if ( $row->rev_user != $userId ) {
index b890e5a..0ff7c13 100644 (file)
@@ -590,4 +590,11 @@ class SqlBlobStore implements IDBAccessObject, BlobStore {
                return [ $schema, $id, $parameters ];
        }
 
+       public function isReadOnly() {
+               if ( $this->useExternalStore && ExternalStore::defaultStoresAreReadOnly() ) {
+                       return true;
+               }
+
+               return ( $this->getDBLoadBalancer()->getReadOnlyReason() !== false );
+       }
 }
index 82d9fd9..6dc7db5 100644 (file)
@@ -4468,17 +4468,18 @@ class Title implements LinkTarget {
                        return $authors;
                }
                $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select( 'revision', 'DISTINCT rev_user_text',
+               $revQuery = Revision::getQueryInfo();
+               $authors = $dbr->selectFieldValues(
+                       $revQuery['tables'],
+                       $revQuery['fields']['rev_user_text'],
                        [
                                'rev_page' => $this->getArticleID(),
                                "rev_timestamp $old_cmp " . $dbr->addQuotes( $dbr->timestamp( $old->getTimestamp() ) ),
                                "rev_timestamp $new_cmp " . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
                        ], __METHOD__,
-                       [ 'LIMIT' => $limit + 1 ] // add one so caller knows it was truncated
+                       [ 'DISTINCT', 'LIMIT' => $limit + 1 ], // add one so caller knows it was truncated
+                       $revQuery['joins']
                );
-               foreach ( $res as $row ) {
-                       $authors[] = $row->rev_user_text;
-               }
                return $authors;
        }
 
index 1165a26..0988f73 100644 (file)
@@ -718,6 +718,8 @@ class InfoAction extends FormlessAction {
                        self::getCacheKey( $cache, $page->getTitle(), $page->getLatest() ),
                        WANObjectCache::TTL_WEEK,
                        function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname ) {
+                               global $wgActorTableSchemaMigrationStage;
+
                                $title = $page->getTitle();
                                $id = $title->getArticleID();
 
@@ -725,6 +727,29 @@ class InfoAction extends FormlessAction {
                                $dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
                                $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
 
+                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                                       $tables = [ 'revision_actor_temp' ];
+                                       $field = 'revactor_actor';
+                                       $pageField = 'revactor_page';
+                                       $tsField = 'revactor_timestamp';
+                                       $joins = [];
+                               } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                                       $tables = [ 'revision' ];
+                                       $field = 'rev_user_text';
+                                       $pageField = 'rev_page';
+                                       $tsField = 'rev_timestamp';
+                                       $joins = [];
+                               } else {
+                                       $tables = [ 'revision', 'revision_actor_temp', 'actor' ];
+                                       $field = 'COALESCE( actor_name, rev_user_text)';
+                                       $pageField = 'rev_page';
+                                       $tsField = 'rev_timestamp';
+                                       $joins = [
+                                               'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ],
+                                               'actor' => [ 'LEFT JOIN', 'revactor_actor = actor_id' ],
+                                       ];
+                               }
+
                                $watchedItemStore = MediaWikiServices::getInstance()->getWatchedItemStore();
 
                                $result = [];
@@ -752,10 +777,12 @@ class InfoAction extends FormlessAction {
                                        $result['authors'] = 0;
                                } else {
                                        $result['authors'] = (int)$dbr->selectField(
-                                               'revision',
-                                               'COUNT(DISTINCT rev_user_text)',
-                                               [ 'rev_page' => $id ],
-                                               $fname
+                                               $tables,
+                                               "COUNT(DISTINCT $field)",
+                                               [ $pageField => $id ],
+                                               $fname,
+                                               [],
+                                               $joins
                                        );
                                }
 
@@ -776,13 +803,15 @@ class InfoAction extends FormlessAction {
 
                                // Recent number of distinct authors
                                $result['recent_authors'] = (int)$dbr->selectField(
-                                       'revision',
-                                       'COUNT(DISTINCT rev_user_text)',
+                                       $tables,
+                                       "COUNT(DISTINCT $field)",
                                        [
-                                               'rev_page' => $id,
-                                               "rev_timestamp >= " . $dbr->addQuotes( $threshold )
+                                               $pageField => $id,
+                                               "$tsField >= " . $dbr->addQuotes( $threshold )
                                        ],
-                                       $fname
+                                       $fname,
+                                       [],
+                                       $joins
                                );
 
                                // Subpages (if enabled)
index 32d081e..f885b72 100644 (file)
@@ -224,10 +224,19 @@ class ApiQueryAllDeletedRevisions extends ApiQueryRevisionsBase {
                }
 
                if ( !is_null( $params['user'] ) ) {
-                       $this->addWhereFld( 'ar_user_text', $params['user'] );
+                       // Don't query by user ID here, it might be able to use the ar_usertext_timestamp index.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'ar_user', User::newFromName( $params['user'], false ), false );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( $actorQuery['conds'] );
                } elseif ( !is_null( $params['excludeuser'] ) ) {
-                       $this->addWhere( 'ar_user_text != ' .
-                               $db->addQuotes( $params['excludeuser'] ) );
+                       // Here there's no chance of using ar_usertext_timestamp.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'ar_user', User::newFromName( $params['excludeuser'], false ) );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
                }
 
                if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
index dde22d8..14f1cc4 100644 (file)
@@ -85,7 +85,6 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                $db = $this->getDB();
 
                $params = $this->extractRequestParams();
-               $userId = !is_null( $params['user'] ) ? User::idFromName( $params['user'] ) : null;
 
                // Table and return fields
                $prop = array_flip( $params['prop'] );
@@ -192,19 +191,22 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
 
                        // Image filters
                        if ( !is_null( $params['user'] ) ) {
-                               if ( $userId ) {
-                                       $this->addWhereFld( 'img_user', $userId );
-                               } else {
-                                       $this->addWhereFld( 'img_user_text', $params['user'] );
-                               }
+                               $actorQuery = ActorMigration::newMigration()
+                                       ->getWhere( $db, 'img_user', User::newFromName( $params['user'], false ) );
+                               $this->addTables( $actorQuery['tables'] );
+                               $this->addJoinConds( $actorQuery['joins'] );
+                               $this->addWhere( $actorQuery['conds'] );
                        }
                        if ( $params['filterbots'] != 'all' ) {
+                               $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
+                               $this->addTables( $actorQuery['tables'] );
                                $this->addTables( 'user_groups' );
+                               $this->addJoinConds( $actorQuery['joins'] );
                                $this->addJoinConds( [ 'user_groups' => [
                                        'LEFT JOIN',
                                        [
                                                'ug_group' => User::getGroupsWithPermission( 'bot' ),
-                                               'ug_user = img_user',
+                                               'ug_user = ' . $actorQuery['fields']['img_user'],
                                                'ug_expiry IS NULL OR ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
                                        ]
                                ] ] );
@@ -273,15 +275,6 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase {
                }
                if ( $params['sort'] == 'timestamp' ) {
                        $this->addOption( 'ORDER BY', 'img_timestamp' . $sortFlag );
-                       if ( !is_null( $params['user'] ) ) {
-                               if ( $userId ) {
-                                       $this->addOption( 'USE INDEX', [ 'image' => 'img_user_timestamp' ] );
-                               } else {
-                                       $this->addOption( 'USE INDEX', [ 'image' => 'img_usertext_timestamp' ] );
-                               }
-                       } else {
-                               $this->addOption( 'USE INDEX', [ 'image' => 'img_timestamp' ] );
-                       }
                } else {
                        $this->addOption( 'ORDER BY', 'img_name' . $sortFlag );
                }
index 6823646..3af2459 100644 (file)
@@ -104,19 +104,17 @@ class ApiQueryAllRevisions extends ApiQueryRevisionsBase {
                }
 
                if ( $params['user'] !== null ) {
-                       $id = User::idFromName( $params['user'] );
-                       if ( $id ) {
-                               $this->addWhereFld( 'rev_user', $id );
-                       } else {
-                               $this->addWhereFld( 'rev_user_text', $params['user'] );
-                       }
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'rev_user', User::newFromName( $params['user'], false ) );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( $actorQuery['conds'] );
                } elseif ( $params['excludeuser'] !== null ) {
-                       $id = User::idFromName( $params['excludeuser'] );
-                       if ( $id ) {
-                               $this->addWhere( 'rev_user != ' . $id );
-                       } else {
-                               $this->addWhere( 'rev_user_text != ' . $db->addQuotes( $params['excludeuser'] ) );
-                       }
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'rev_user', User::newFromName( $params['excludeuser'], false ) );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
                }
 
                if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
index 26844f3..9652f81 100644 (file)
@@ -41,6 +41,8 @@ class ApiQueryAllUsers extends ApiQueryBase {
        }
 
        public function execute() {
+               global $wgActorTableSchemaMigrationStage;
+
                $params = $this->extractRequestParams();
                $activeUserDays = $this->getConfig()->get( 'ActiveUserDays' );
 
@@ -178,17 +180,36 @@ class ApiQueryAllUsers extends ApiQueryBase {
                        ] ] );
 
                        // Actually count the actions using a subquery (T66505 and T66507)
+                       $tables = [ 'recentchanges' ];
+                       $joins = [];
+                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                               $userCond = 'rc_user_text = user_name';
+                       } else {
+                               $tables[] = 'actor';
+                               $joins['actor'] = [
+                                       $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                                       'rc_actor = actor_id'
+                               ];
+                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                                       $userCond = 'actor_user = user_id';
+                               } else {
+                                       $userCond = 'actor_user = user_id OR (rc_actor = 0 AND rc_user_text = user_name)';
+                               }
+                       }
                        $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
                        $this->addFields( [
                                'recentactions' => '(' . $db->selectSQLText(
-                                       'recentchanges',
+                                       $tables,
                                        'COUNT(*)',
                                        [
-                                               'rc_user_text = user_name',
+                                               $userCond,
                                                'rc_type != ' . $db->addQuotes( RC_EXTERNAL ), // no wikidata
                                                'rc_log_type IS NULL OR rc_log_type != ' . $db->addQuotes( 'newusers' ),
                                                'rc_timestamp >= ' . $db->addQuotes( $timestamp ),
-                                       ]
+                                       ],
+                                       __METHOD__,
+                                       [],
+                                       $joins
                                ) . ')'
                        ] );
                }
index 84169cb..3ad45bb 100644 (file)
@@ -446,11 +446,13 @@ abstract class ApiQueryBase extends ApiBase {
                if ( $showBlockInfo ) {
                        $this->addFields( [
                                'ipb_id',
-                               'ipb_by',
-                               'ipb_by_text',
                                'ipb_expiry',
                                'ipb_timestamp'
                        ] );
+                       $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addFields( $actorQuery['fields'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
                        $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' );
                        $this->addTables( $commentQuery['tables'] );
                        $this->addFields( $commentQuery['fields'] );
index 10695b3..08c13e7 100644 (file)
@@ -55,8 +55,12 @@ class ApiQueryBlocks extends ApiQueryBase {
                $this->addFields( [ 'ipb_auto', 'ipb_id', 'ipb_timestamp' ] );
 
                $this->addFieldsIf( [ 'ipb_address', 'ipb_user' ], $fld_user || $fld_userid );
-               $this->addFieldsIf( 'ipb_by_text', $fld_by );
-               $this->addFieldsIf( 'ipb_by', $fld_byid );
+               if ( $fld_by || $fld_byid ) {
+                       $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addFields( $actorQuery['fields'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+               }
                $this->addFieldsIf( 'ipb_expiry', $fld_expiry );
                $this->addFieldsIf( [ 'ipb_range_start', 'ipb_range_end' ], $fld_range );
                $this->addFieldsIf( [ 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock',
index 25b7c84..48516a7 100644 (file)
@@ -43,6 +43,8 @@ class ApiQueryContributors extends ApiQueryBase {
        }
 
        public function execute() {
+               global $wgActorTableSchemaMigrationStage;
+
                $db = $this->getDB();
                $params = $this->extractRequestParams();
                $this->requireMaxOneParameter( $params, 'group', 'excludegroup', 'rights', 'excluderights' );
@@ -73,17 +75,27 @@ class ApiQueryContributors extends ApiQueryBase {
                }
 
                $result = $this->getResult();
+               $revQuery = Revision::getQueryInfo();
+
+               // For MIGRATION_NEW, target indexes on the revision_actor_temp table.
+               // Otherwise, revision is fine because it'll have to check all revision rows anyway.
+               $pageField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'revactor_page' : 'rev_page';
+               $idField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW
+                       ? 'revactor_actor' : $revQuery['fields']['rev_user'];
+               $countField = $wgActorTableSchemaMigrationStage === MIGRATION_NEW
+                       ? 'revactor_actor' : $revQuery['fields']['rev_user_text'];
 
                // First, count anons
-               $this->addTables( 'revision' );
+               $this->addTables( $revQuery['tables'] );
+               $this->addJoinConds( $revQuery['joins'] );
                $this->addFields( [
-                       'page' => 'rev_page',
-                       'anons' => 'COUNT(DISTINCT rev_user_text)',
+                       'page' => $pageField,
+                       'anons' => "COUNT(DISTINCT $countField)",
                ] );
-               $this->addWhereFld( 'rev_page', $pages );
-               $this->addWhere( 'rev_user = 0' );
+               $this->addWhereFld( $pageField, $pages );
+               $this->addWhere( ActorMigration::newMigration()->isAnon( $revQuery['fields']['rev_user'] ) );
                $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
-               $this->addOption( 'GROUP BY', 'rev_page' );
+               $this->addOption( 'GROUP BY', $pageField );
                $res = $this->select( __METHOD__ );
                foreach ( $res as $row ) {
                        $fit = $result->addValue( [ 'query', 'pages', $row->page ],
@@ -103,24 +115,27 @@ class ApiQueryContributors extends ApiQueryBase {
 
                // Next, add logged-in users
                $this->resetQueryParams();
-               $this->addTables( 'revision' );
+               $this->addTables( $revQuery['tables'] );
+               $this->addJoinConds( $revQuery['joins'] );
                $this->addFields( [
-                       'page' => 'rev_page',
-                       'user' => 'rev_user',
-                       'username' => 'MAX(rev_user_text)', // Non-MySQL databases don't like partial group-by
+                       'page' => $pageField,
+                       'id' => $idField,
+                       // Non-MySQL databases don't like partial group-by
+                       'userid' => 'MAX(' . $revQuery['fields']['rev_user'] . ')',
+                       'username' => 'MAX(' . $revQuery['fields']['rev_user_text'] . ')',
                ] );
-               $this->addWhereFld( 'rev_page', $pages );
-               $this->addWhere( 'rev_user != 0' );
+               $this->addWhereFld( $pageField, $pages );
+               $this->addWhere( ActorMigration::newMigration()->isNotAnon( $revQuery['fields']['rev_user'] ) );
                $this->addWhere( $db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' );
-               $this->addOption( 'GROUP BY', 'rev_page, rev_user' );
+               $this->addOption( 'GROUP BY', [ $pageField, $idField ] );
                $this->addOption( 'LIMIT', $params['limit'] + 1 );
 
                // Force a sort order to ensure that properties are grouped by page
-               // But only if pp_page is not constant in the WHERE clause.
+               // But only if rev_page is not constant in the WHERE clause.
                if ( count( $pages ) > 1 ) {
-                       $this->addOption( 'ORDER BY', 'rev_page, rev_user' );
+                       $this->addOption( 'ORDER BY', [ 'page', 'id' ] );
                } else {
-                       $this->addOption( 'ORDER BY', 'rev_user' );
+                       $this->addOption( 'ORDER BY', 'id' );
                }
 
                $limitGroups = [];
@@ -159,7 +174,7 @@ class ApiQueryContributors extends ApiQueryBase {
                        $this->addJoinConds( [ 'user_groups' => [
                                $excludeGroups ? 'LEFT OUTER JOIN' : 'INNER JOIN',
                                [
-                                       'ug_user=rev_user',
+                                       'ug_user=' . $actorQuery['fields']['rev_user'],
                                        'ug_group' => $limitGroups,
                                        'ug_expiry IS NULL OR ug_expiry >= ' . $db->addQuotes( $db->timestamp() )
                                ]
@@ -171,11 +186,11 @@ class ApiQueryContributors extends ApiQueryBase {
                        $cont = explode( '|', $params['continue'] );
                        $this->dieContinueUsageIf( count( $cont ) != 2 );
                        $cont_page = (int)$cont[0];
-                       $cont_user = (int)$cont[1];
+                       $cont_id = (int)$cont[1];
                        $this->addWhere(
-                               "rev_page > $cont_page OR " .
-                               "(rev_page = $cont_page AND " .
-                               "rev_user >= $cont_user)"
+                               "$pageField > $cont_page OR " .
+                               "($pageField = $cont_page AND " .
+                               "$idField >= $cont_id)"
                        );
                }
 
@@ -185,18 +200,16 @@ class ApiQueryContributors extends ApiQueryBase {
                        if ( ++$count > $params['limit'] ) {
                                // We've reached the one extra which shows that
                                // there are additional pages to be had. Stop here...
-                               $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->user );
-
+                               $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->id );
                                return;
                        }
 
                        $fit = $this->addPageSubItem( $row->page,
-                               [ 'userid' => (int)$row->user, 'name' => $row->username ],
+                               [ 'userid' => (int)$row->userid, 'name' => $row->username ],
                                'user'
                        );
                        if ( !$fit ) {
-                               $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->user );
-
+                               $this->setContinueEnumParameter( 'continue', $row->page . '|' . $row->id );
                                return;
                        }
                }
index f579065..b7fd8d4 100644 (file)
@@ -117,10 +117,19 @@ class ApiQueryDeletedRevisions extends ApiQueryRevisionsBase {
                }
 
                if ( !is_null( $params['user'] ) ) {
-                       $this->addWhereFld( 'ar_user_text', $params['user'] );
+                       // Don't query by user ID here, it might be able to use the ar_usertext_timestamp index.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'ar_user', User::newFromName( $params['user'], false ), false );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( $actorQuery['conds'] );
                } elseif ( !is_null( $params['excludeuser'] ) ) {
-                       $this->addWhere( 'ar_user_text != ' .
-                               $db->addQuotes( $params['excludeuser'] ) );
+                       // Here there's no chance of using ar_usertext_timestamp.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'ar_user', User::newFromName( $params['excludeuser'], false ) );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
                }
 
                if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
index 6e6757e..2d50741 100644 (file)
@@ -110,8 +110,12 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
 
                $this->addFieldsIf( 'ar_parent_id', $fld_parentid );
                $this->addFieldsIf( 'ar_rev_id', $fld_revid );
-               $this->addFieldsIf( 'ar_user_text', $fld_user );
-               $this->addFieldsIf( 'ar_user', $fld_userid );
+               if ( $fld_user || $fld_userid ) {
+                       $actorQuery = ActorMigration::newMigration()->getJoin( 'ar_user' );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addFields( $actorQuery['fields'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+               }
                $this->addFieldsIf( 'ar_minor_edit', $fld_minor );
                $this->addFieldsIf( 'ar_len', $fld_len );
                $this->addFieldsIf( 'ar_sha1', $fld_sha1 );
@@ -199,10 +203,19 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                }
 
                if ( !is_null( $params['user'] ) ) {
-                       $this->addWhereFld( 'ar_user_text', $params['user'] );
+                       // Don't query by user ID here, it might be able to use the ar_usertext_timestamp index.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'ar_user', User::newFromName( $params['user'], false ), false );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( $actorQuery['conds'] );
                } elseif ( !is_null( $params['excludeuser'] ) ) {
-                       $this->addWhere( 'ar_user_text != ' .
-                               $db->addQuotes( $params['excludeuser'] ) );
+                       // Here there's no chance of using ar_usertext_timestamp.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $db, 'ar_user', User::newFromName( $params['excludeuser'], false ) );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
                }
 
                if ( !is_null( $params['user'] ) || !is_null( $params['excludeuser'] ) ) {
@@ -251,10 +264,6 @@ class ApiQueryDeletedrevs extends ApiQueryBase {
                }
 
                $this->addOption( 'LIMIT', $limit + 1 );
-               $this->addOption(
-                       'USE INDEX',
-                       [ 'archive' => ( $mode == 'user' ? 'ar_usertext_timestamp' : 'name_title_timestamp' ) ]
-               );
                if ( $mode == 'all' ) {
                        if ( $params['unique'] ) {
                                // @todo Does this work on non-MySQL?
index f345300..68902a3 100644 (file)
@@ -62,11 +62,15 @@ class ApiQueryLogEvents extends ApiQueryBase {
                        $this->addWhere( $hideLogs );
                }
 
-               // Order is significant here
-               $this->addTables( [ 'logging', 'user', 'page' ] );
+               $actorMigration = ActorMigration::newMigration();
+               $actorQuery = $actorMigration->getJoin( 'log_user' );
+               $this->addTables( 'logging' );
+               $this->addTables( $actorQuery['tables'] );
+               $this->addTables( [ 'user', 'page' ] );
+               $this->addJoinConds( $actorQuery['joins'] );
                $this->addJoinConds( [
                        'user' => [ 'LEFT JOIN',
-                               'user_id=log_user' ],
+                               'user_id=' . $actorQuery['fields']['log_user'] ],
                        'page' => [ 'LEFT JOIN',
                                [ 'log_namespace=page_namespace',
                                        'log_title=page_title' ] ] ] );
@@ -84,8 +88,8 @@ class ApiQueryLogEvents extends ApiQueryBase {
                // join at query time.  This leads to different results in various
                // scenarios, e.g. deletion, recreation.
                $this->addFieldsIf( 'log_page', $this->fld_ids );
-               $this->addFieldsIf( [ 'log_user', 'log_user_text', 'user_name' ], $this->fld_user );
-               $this->addFieldsIf( 'log_user', $this->fld_userid );
+               $this->addFieldsIf( $actorQuery['fields'] + [ 'user_name' ], $this->fld_user );
+               $this->addFieldsIf( $actorQuery['fields'], $this->fld_userid );
                $this->addFieldsIf(
                        [ 'log_namespace', 'log_title' ],
                        $this->fld_title || $this->fld_parsedcomment
@@ -166,12 +170,14 @@ class ApiQueryLogEvents extends ApiQueryBase {
 
                $user = $params['user'];
                if ( !is_null( $user ) ) {
-                       $userid = User::idFromName( $user );
-                       if ( $userid ) {
-                               $this->addWhereFld( 'log_user', $userid );
-                       } else {
-                               $this->addWhereFld( 'log_user_text', $user );
-                       }
+                       // Note the joins in $q are the same as those from ->getJoin() above
+                       // so we only need to add 'conds' here.
+                       // Don't query by user ID here, it might be able to use the
+                       // log_user_text_time or log_user_text_type_time index.
+                       $q = $actorMigration->getWhere(
+                               $db, 'log_user', User::newFromName( $params['user'], false ), false
+                       );
+                       $this->addWhere( $q['conds'] );
                }
 
                $title = $params['title'];
index e289e42..e431202 100644 (file)
@@ -211,8 +211,18 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                        $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
                        $this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
                        $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
-                       $this->addWhereIf( 'rc_user = 0', isset( $show['anon'] ) );
-                       $this->addWhereIf( 'rc_user != 0', isset( $show['!anon'] ) );
+                       if ( isset( $show['anon'] ) || isset( $show['!anon'] ) ) {
+                               $actorMigration = ActorMigration::newMigration();
+                               $actorQuery = $actorMigration->getJoin( 'rc_user' );
+                               $this->addTables( $actorQuery['tables'] );
+                               $this->addJoinConds( $actorQuery['joins'] );
+                               $this->addWhereIf(
+                                       $actorMigration->isAnon( $actorQuery['fields']['rc_user'] ), isset( $show['anon'] )
+                               );
+                               $this->addWhereIf(
+                                       $actorMigration->isNotAnon( $actorQuery['fields']['rc_user'] ), isset( $show['!anon'] )
+                               );
+                       }
                        $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
                        $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
                        $this->addWhereIf( 'page_is_redirect = 1', isset( $show['redirect'] ) );
@@ -237,14 +247,21 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
                $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
 
                if ( !is_null( $params['user'] ) ) {
-                       $this->addWhereFld( 'rc_user_text', $params['user'] );
+                       // Don't query by user ID here, it might be able to use the rc_user_text index.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $this->getDB(), 'rc_user', User::newFromName( $params['user'], false ), false );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( $actorQuery['conds'] );
                }
 
                if ( !is_null( $params['excludeuser'] ) ) {
-                       // We don't use the rc_user_text index here because
-                       // * it would require us to sort by rc_user_text before rc_timestamp
-                       // * the != condition doesn't throw out too many rows anyway
-                       $this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
+                       // Here there's no chance to use the rc_user_text index, so allow ID to be used.
+                       $actorQuery = ActorMigration::newMigration()
+                               ->getWhere( $this->getDB(), 'rc_user', User::newFromName( $params['excludeuser'], false ) );
+                       $this->addTables( $actorQuery['tables'] );
+                       $this->addJoinConds( $actorQuery['joins'] );
+                       $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
                }
 
                /* Add the fields we're concerned with to our query. */
@@ -272,8 +289,12 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase {
 
                        /* Add fields to our query if they are specified as a needed parameter. */
                        $this->addFieldsIf( [ 'rc_this_oldid', 'rc_last_oldid' ], $this->fld_ids );
-                       $this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid );
-                       $this->addFieldsIf( 'rc_user_text', $this->fld_user );
+                       if ( $this->fld_user || $this->fld_userid ) {
+                               $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
+                               $this->addTables( $actorQuery['tables'] );
+                               $this->addFields( $actorQuery['fields'] );
+                               $this->addJoinConds( $actorQuery['joins'] );
+                       }
                        $this->addFieldsIf( [ 'rc_minor', 'rc_type', 'rc_bot' ], $this->fld_flags );
                        $this->addFieldsIf( [ 'rc_old_len', 'rc_new_len' ], $this->fld_sizes );
                        $this->addFieldsIf( [ 'rc_patrolled', 'rc_log_type' ], $this->fld_patrolled );
index ef0223a..5858bc7 100644 (file)
@@ -286,20 +286,17 @@ class ApiQueryRevisions extends ApiQueryRevisionsBase {
                        $this->addWhereFld( 'rev_page', reset( $ids ) );
 
                        if ( $params['user'] !== null ) {
-                               $user = User::newFromName( $params['user'] );
-                               if ( $user && $user->getId() > 0 ) {
-                                       $this->addWhereFld( 'rev_user', $user->getId() );
-                               } else {
-                                       $this->addWhereFld( 'rev_user_text', $params['user'] );
-                               }
+                               $actorQuery = ActorMigration::newMigration()
+                                       ->getWhere( $db, 'rev_user', User::newFromName( $params['user'], false ) );
+                               $this->addTables( $actorQuery['tables'] );
+                               $this->addJoinConds( $actorQuery['joins'] );
+                               $this->addWhere( $actorQuery['conds'] );
                        } elseif ( $params['excludeuser'] !== null ) {
-                               $user = User::newFromName( $params['excludeuser'] );
-                               if ( $user && $user->getId() > 0 ) {
-                                       $this->addWhere( 'rev_user != ' . $user->getId() );
-                               } else {
-                                       $this->addWhere( 'rev_user_text != ' .
-                                               $db->addQuotes( $params['excludeuser'] ) );
-                               }
+                               $actorQuery = ActorMigration::newMigration()
+                                       ->getWhere( $db, 'rev_user', User::newFromName( $params['excludeuser'], false ) );
+                               $this->addTables( $actorQuery['tables'] );
+                               $this->addJoinConds( $actorQuery['joins'] );
+                               $this->addWhere( 'NOT(' . $actorQuery['conds'] . ')' );
                        }
                        if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
                                // Paranoia: avoid brute force searches (T19342)
index 1705c57..e587ef4 100644 (file)
@@ -31,13 +31,14 @@ class ApiQueryContributions extends ApiQueryBase {
                parent::__construct( $query, $moduleName, 'uc' );
        }
 
-       private $params, $prefixMode, $userprefix, $multiUserMode, $idMode, $usernames, $userids,
-               $parentLens, $commentStore;
+       private $params, $multiUserMode, $orderBy, $parentLens;
        private $fld_ids = false, $fld_title = false, $fld_timestamp = false,
                $fld_comment = false, $fld_parsedcomment = false, $fld_flags = false,
                $fld_patrolled = false, $fld_tags = false, $fld_size = false, $fld_sizediff = false;
 
        public function execute() {
+               global $wgActorTableSchemaMigrationStage;
+
                // Parse some parameters
                $this->params = $this->extractRequestParams();
 
@@ -63,36 +64,173 @@ class ApiQueryContributions extends ApiQueryBase {
                // TODO: if the query is going only against the revision table, should this be done?
                $this->selectNamedDB( 'contributions', DB_REPLICA, 'contributions' );
 
-               $this->requireOnlyOneParameter( $this->params, 'userprefix', 'userids', 'user' );
+               $sort = ( $this->params['dir'] == 'newer' ? '' : ' DESC' );
+               $op = ( $this->params['dir'] == 'older' ? '<' : '>' );
 
-               $this->idMode = false;
+               // Create an Iterator that produces the UserIdentity objects we need, depending
+               // on which of the 'userprefix', 'userids', or 'user' params was
+               // specified.
+               $this->requireOnlyOneParameter( $this->params, 'userprefix', 'userids', 'user' );
                if ( isset( $this->params['userprefix'] ) ) {
-                       $this->prefixMode = true;
                        $this->multiUserMode = true;
-                       $this->userprefix = $this->params['userprefix'];
-               } elseif ( isset( $this->params['userids'] ) ) {
-                       $this->userids = [];
+                       $this->orderBy = 'name';
+                       $fname = __METHOD__;
+
+                       // Because 'userprefix' might produce a huge number of users (e.g.
+                       // a wiki with users "Test00000001" to "Test99999999"), use a
+                       // generator with batched lookup and continuation.
+                       $userIter = call_user_func( function () use ( $dbSecondary, $sort, $op, $fname ) {
+                               global $wgActorTableSchemaMigrationStage;
+
+                               $from = $fromName = false;
+                               if ( !is_null( $this->params['continue'] ) ) {
+                                       $continue = explode( '|', $this->params['continue'] );
+                                       $this->dieContinueUsageIf( count( $continue ) != 4 );
+                                       $this->dieContinueUsageIf( $continue[0] !== 'name' );
+                                       $fromName = $continue[1];
+                                       $from = "$op= " . $dbSecondary->addQuotes( $fromName );
+                               }
+                               $like = $dbSecondary->buildLike( $this->params['userprefix'], $dbSecondary->anyString() );
+
+                               $limit = 501;
+
+                               do {
+                                       // For the new schema, pull from the actor table. For the
+                                       // old, pull from rev_user. For migration a FULL [OUTER]
+                                       // JOIN would be what we want, except MySQL doesn't support
+                                       // that so we have to UNION instead.
+                                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                                               $res = $dbSecondary->select(
+                                                       'actor',
+                                                       [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
+                                                       array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
+                                                       $fname,
+                                                       [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ]
+                                               );
+                                       } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                                               $res = $dbSecondary->select(
+                                                       'revision',
+                                                       [ 'actor_id' => 'NULL', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
+                                                       array_merge( [ "rev_user_text$like" ], $from ? [ "rev_user_text $from" ] : [] ),
+                                                       $fname,
+                                                       [ 'DISTINCT', 'ORDER BY' => [ "rev_user_text $sort" ], 'LIMIT' => $limit ]
+                                               );
+                                       } else {
+                                               // There are three queries we have to combine to be sure of getting all results:
+                                               //  - actor table (any rows that have been migrated will have empty rev_user_text)
+                                               //  - revision+actor by user id
+                                               //  - revision+actor by name for anons
+                                               $options = $dbSecondary->unionSupportsOrderAndLimit()
+                                                       ? [ 'ORDER BY' => [ "user_name $sort" ], 'LIMIT' => $limit ] : [];
+                                               $subsql = [];
+                                               $subsql[] = $dbSecondary->selectSQLText(
+                                                       'actor',
+                                                       [ 'actor_id', 'user_id' => 'COALESCE(actor_user,0)', 'user_name' => 'actor_name' ],
+                                                       array_merge( [ "actor_name$like" ], $from ? [ "actor_name $from" ] : [] ),
+                                                       $fname,
+                                                       $options
+                                               );
+                                               $subsql[] = $dbSecondary->selectSQLText(
+                                                       [ 'revision', 'actor' ],
+                                                       [ 'actor_id', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
+                                                       array_merge(
+                                                               [ "rev_user_text$like", 'rev_user != 0' ],
+                                                               $from ? [ "rev_user_text $from" ] : []
+                                                       ),
+                                                       $fname,
+                                                       array_merge( [ 'DISTINCT' ], $options ),
+                                                       [ 'actor' => [ 'LEFT JOIN', 'rev_user = actor_user' ] ]
+                                               );
+                                               $subsql[] = $dbSecondary->selectSQLText(
+                                                       [ 'revision', 'actor' ],
+                                                       [ 'actor_id', 'user_id' => 'rev_user', 'user_name' => 'rev_user_text' ],
+                                                       array_merge(
+                                                               [ "rev_user_text$like", 'rev_user = 0' ],
+                                                               $from ? [ "rev_user_text $from" ] : []
+                                                       ),
+                                                       $fname,
+                                                       array_merge( [ 'DISTINCT' ], $options ),
+                                                       [ 'actor' => [ 'LEFT JOIN', 'rev_user_text = actor_name' ] ]
+                                               );
+                                               $sql = $dbSecondary->unionQueries( $subsql, false ) . " ORDER BY user_name $sort";
+                                               $sql = $dbSecondary->limitResult( $sql, $limit );
+                                               $res = $dbSecondary->query( $sql, $fname );
+                                       }
 
+                                       $count = 0;
+                                       $from = null;
+                                       foreach ( $res as $row ) {
+                                               if ( ++$count >= $limit ) {
+                                                       $from = $row->user_name;
+                                                       break;
+                                               }
+                                               yield User::newFromRow( $row );
+                                       }
+                               } while ( $from !== null );
+                       } );
+                       // Do the actual sorting client-side, because otherwise
+                       // prepareQuery might try to sort by actor and confuse everything.
+                       $batchSize = 1;
+               } elseif ( isset( $this->params['userids'] ) ) {
                        if ( !count( $this->params['userids'] ) ) {
                                $encParamName = $this->encodeParamName( 'userids' );
                                $this->dieWithError( [ 'apierror-paramempty', $encParamName ], "paramempty_$encParamName" );
                        }
 
+                       $ids = [];
                        foreach ( $this->params['userids'] as $uid ) {
                                if ( $uid <= 0 ) {
                                        $this->dieWithError( [ 'apierror-invaliduserid', $uid ], 'invaliduserid' );
                                }
+                               $ids[] = $uid;
+                       }
+
+                       $this->orderBy = 'id';
+                       $this->multiUserMode = count( $ids ) > 1;
 
-                               $this->userids[] = $uid;
+                       $from = $fromId = false;
+                       if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
+                               $continue = explode( '|', $this->params['continue'] );
+                               $this->dieContinueUsageIf( count( $continue ) != 4 );
+                               $this->dieContinueUsageIf( $continue[0] !== 'id' && $continue[0] !== 'actor' );
+                               $fromId = (int)$continue[1];
+                               $this->dieContinueUsageIf( $continue[1] !== (string)$fromId );
+                               $from = "$op= $fromId";
                        }
 
-                       $this->prefixMode = false;
-                       $this->multiUserMode = ( count( $this->params['userids'] ) > 1 );
-                       $this->idMode = true;
+                       // For the new schema, just select from the actor table. For the
+                       // old and transitional schemas, select from user and left join
+                       // actor if it exists.
+                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                               $res = $dbSecondary->select(
+                                       'actor',
+                                       [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
+                                       array_merge( [ 'actor_user' => $ids ], $from ? [ "actor_id $from" ] : [] ),
+                                       __METHOD__,
+                                       [ 'ORDER BY' => "user_id $sort" ]
+                               );
+                       } elseif ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                               $res = $dbSecondary->select(
+                                       'user',
+                                       [ 'actor_id' => 'NULL', 'user_id' => 'user_id', 'user_name' => 'user_name' ],
+                                       array_merge( [ 'user_id' => $ids ], $from ? [ "user_id $from" ] : [] ),
+                                       __METHOD__,
+                                       [ 'ORDER BY' => "user_id $sort" ]
+                               );
+                       } else {
+                               $res = $dbSecondary->select(
+                                       [ 'user', 'actor' ],
+                                       [ 'actor_id', 'user_id', 'user_name' ],
+                                       array_merge( [ 'user_id' => $ids ], $from ? [ "user_id $from" ] : [] ),
+                                       __METHOD__,
+                                       [ 'ORDER BY' => "user_id $sort" ],
+                                       [ 'actor' => [ 'LEFT JOIN', 'actor_user = user_id' ] ]
+                               );
+                       }
+                       $userIter = UserArray::newFromResult( $res );
+                       $batchSize = count( $ids );
                } else {
-                       $anyIPs = false;
-                       $this->userids = [];
-                       $this->usernames = [];
+                       $names = [];
                        if ( !count( $this->params['user'] ) ) {
                                $encParamName = $this->encodeParamName( 'user' );
                                $this->dieWithError(
@@ -108,8 +246,7 @@ class ApiQueryContributions extends ApiQueryBase {
                                }
 
                                if ( User::isIP( $u ) ) {
-                                       $anyIPs = true;
-                                       $this->usernames[] = $u;
+                                       $names[$u] = null;
                                } else {
                                        $name = User::getCanonicalName( $u, 'valid' );
                                        if ( $name === false ) {
@@ -118,94 +255,218 @@ class ApiQueryContributions extends ApiQueryBase {
                                                        [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $u ) ], "baduser_$encParamName"
                                                );
                                        }
-                                       $this->usernames[] = $name;
+                                       $names[$name] = null;
                                }
                        }
-                       $this->prefixMode = false;
-                       $this->multiUserMode = ( count( $this->params['user'] ) > 1 );
 
-                       if ( !$anyIPs ) {
-                               $dbr = $this->getDB();
-                               $res = $dbr->select( 'user', 'user_id', [ 'user_name' => $this->usernames ], __METHOD__ );
+                       $this->orderBy = 'name';
+                       $this->multiUserMode = count( $names ) > 1;
+
+                       $from = $fromName = false;
+                       if ( $this->multiUserMode && !is_null( $this->params['continue'] ) ) {
+                               $continue = explode( '|', $this->params['continue'] );
+                               $this->dieContinueUsageIf( count( $continue ) != 4 );
+                               $this->dieContinueUsageIf( $continue[0] !== 'name' && $continue[0] !== 'actor' );
+                               $fromName = $continue[1];
+                               $from = "$op= " . $dbSecondary->addQuotes( $fromName );
+                       }
+
+                       // For the new schema, just select from the actor table. For the
+                       // old and transitional schemas, select from user and left join
+                       // actor if it exists then merge in any unknown users (IPs and imports).
+                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                               $res = $dbSecondary->select(
+                                       'actor',
+                                       [ 'actor_id', 'user_id' => 'actor_user', 'user_name' => 'actor_name' ],
+                                       array_merge( [ 'actor_name' => array_keys( $names ) ], $from ? [ "actor_id $from" ] : [] ),
+                                       __METHOD__,
+                                       [ 'ORDER BY' => "actor_name $sort" ]
+                               );
+                               $userIter = UserArray::newFromResult( $res );
+                       } else {
+                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_OLD ) {
+                                       $res = $dbSecondary->select(
+                                               'user',
+                                               [ 'actor_id' => 'NULL', 'user_id', 'user_name' ],
+                                               array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
+                                               __METHOD__
+                                       );
+                               } else {
+                                       $res = $dbSecondary->select(
+                                               [ 'user', 'actor' ],
+                                               [ 'actor_id', 'user_id', 'user_name' ],
+                                               array_merge( [ 'user_name' => array_keys( $names ) ], $from ? [ "user_name $from" ] : [] ),
+                                               __METHOD__,
+                                               [],
+                                               [ 'actor' => [ 'LEFT JOIN', 'actor_user = user_id' ] ]
+                                       );
+                               }
                                foreach ( $res as $row ) {
-                                       $this->userids[] = $row->user_id;
+                                       $names[$row->user_name] = $row;
                                }
-                               $this->idMode = count( $this->userids ) === count( $this->usernames );
+                               call_user_func_array(
+                                       $this->params['dir'] == 'newer' ? 'ksort' : 'krsort', [ &$names, SORT_STRING ]
+                               );
+                               $neg = $op === '>' ? -1 : 1;
+                               $userIter = call_user_func( function () use ( $names, $fromName, $neg ) {
+                                       foreach ( $names as $name => $row ) {
+                                               if ( $fromName === false || $neg * strcmp( $name, $fromName ) <= 0 ) {
+                                                       $user = $row ? User::newFromRow( $row ) : User::newFromName( $name, false );
+                                                       yield $user;
+                                               }
+                                       }
+                               } );
                        }
+                       $batchSize = count( $names );
                }
 
-               $this->prepareQuery();
-
-               $hookData = [];
-               // Do the actual query.
-               $res = $this->select( __METHOD__, [], $hookData );
+               // During migration, force ordering on the client side because we're
+               // having to combine multiple queries that would otherwise have
+               // different sort orders.
+               if ( $wgActorTableSchemaMigrationStage === MIGRATION_WRITE_BOTH ||
+                       $wgActorTableSchemaMigrationStage === MIGRATION_WRITE_NEW
+               ) {
+                       $batchSize = 1;
+               }
 
-               if ( $this->fld_sizediff ) {
-                       $revIds = [];
-                       foreach ( $res as $row ) {
-                               if ( $row->rev_parent_id ) {
-                                       $revIds[] = $row->rev_parent_id;
-                               }
-                       }
-                       $this->parentLens = Revision::getParentLengths( $dbSecondary, $revIds );
-                       $res->rewind(); // reset
+               // With the new schema, the DB query will order by actor so update $this->orderBy to match.
+               if ( $batchSize > 1 && $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                       $this->orderBy = 'actor';
                }
 
-               // Initialise some variables
                $count = 0;
                $limit = $this->params['limit'];
+               $userIter->rewind();
+               while ( $userIter->valid() ) {
+                       $users = [];
+                       while ( count( $users ) < $batchSize && $userIter->valid() ) {
+                               $users[] = $userIter->current();
+                               $userIter->next();
+                       }
+
+                       // Ugh. We have to run the query three times, once for each
+                       // possible 'orcond' from ActorMigration, and then merge them all
+                       // together in the proper order. And preserving the correct
+                       // $hookData for each one.
+                       // @todo When ActorMigration is removed, this can go back to a
+                       //  single prepare and select.
+                       $merged = [];
+                       foreach ( [ 'actor', 'userid', 'username' ] as $which ) {
+                               if ( $this->prepareQuery( $users, $limit - $count, $which ) ) {
+                                       $hookData = [];
+                                       $res = $this->select( __METHOD__, [], $hookData );
+                                       foreach ( $res as $row ) {
+                                               $merged[] = [ $row, &$hookData ];
+                                       }
+                               }
+                       }
+                       $neg = $this->params['dir'] == 'newer' ? 1 : -1;
+                       usort( $merged, function ( $a, $b ) use ( $neg, $batchSize ) {
+                               if ( $batchSize === 1 ) { // One user, can't be different
+                                       $ret = 0;
+                               } elseif ( $this->orderBy === 'id' ) {
+                                       $ret = $a[0]->rev_user - $b[0]->rev_user;
+                               } elseif ( $this->orderBy === 'name' ) {
+                                       $ret = strcmp( $a[0]->rev_user_text, $b[0]->rev_user_text );
+                               } else {
+                                       $ret = $a[0]->rev_actor - $b[0]->rev_actor;
+                               }
+
+                               if ( !$ret ) {
+                                       $ret = strcmp(
+                                               wfTimestamp( TS_MW, $a[0]->rev_timestamp ),
+                                               wfTimestamp( TS_MW, $b[0]->rev_timestamp )
+                                       );
+                               }
+
+                               if ( !$ret ) {
+                                       $ret = $a[0]->rev_id - $b[0]->rev_id;
+                               }
 
-               // Fetch each row
-               foreach ( $res as $row ) {
-                       if ( ++$count > $limit ) {
-                               // We've reached the one extra which shows that there are
-                               // additional pages to be had. Stop here...
-                               $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
-                               break;
+                               return $neg * $ret;
+                       } );
+                       $merged = array_slice( $merged, 0, $limit - $count + 1 );
+                       // (end "Ugh")
+
+                       if ( $this->fld_sizediff ) {
+                               $revIds = [];
+                               foreach ( $merged as $data ) {
+                                       if ( $data[0]->rev_parent_id ) {
+                                               $revIds[] = $data[0]->rev_parent_id;
+                                       }
+                               }
+                               $this->parentLens = Revision::getParentLengths( $dbSecondary, $revIds );
                        }
 
-                       $vals = $this->extractRowInfo( $row );
-                       $fit = $this->processRow( $row, $vals, $hookData ) &&
-                               $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
-                       if ( !$fit ) {
-                               $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
-                               break;
+                       foreach ( $merged as $data ) {
+                               $row = $data[0];
+                               $hookData = &$data[1];
+                               if ( ++$count > $limit ) {
+                                       // We've reached the one extra which shows that there are
+                                       // additional pages to be had. Stop here...
+                                       $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
+                                       break 2;
+                               }
+
+                               $vals = $this->extractRowInfo( $row );
+                               $fit = $this->processRow( $row, $vals, $hookData ) &&
+                                       $this->getResult()->addValue( [ 'query', $this->getModuleName() ], null, $vals );
+                               if ( !$fit ) {
+                                       $this->setContinueEnumParameter( 'continue', $this->continueStr( $row ) );
+                                       break 2;
+                               }
                        }
                }
 
-               $this->getResult()->addIndexedTagName(
-                       [ 'query', $this->getModuleName() ],
-                       'item'
-               );
+               $this->getResult()->addIndexedTagName( [ 'query', $this->getModuleName() ], 'item' );
        }
 
        /**
         * Prepares the query and returns the limit of rows requested
+        * @param User[] $users
+        * @param int $limit
+        * @param string $which 'actor', 'userid', or 'username'
+        * @return bool
         */
-       private function prepareQuery() {
-               // We're after the revision table, and the corresponding page
-               // row for anything we retrieve. We may also need the
-               // recentchanges row and/or tag summary row.
-               $user = $this->getUser();
-               $tables = [ 'page', 'revision' ]; // Order may change
-               $this->addWhere( 'page_id=rev_page' );
+       private function prepareQuery( array $users, $limit, $which ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               $this->resetQueryParams();
+               $db = $this->getDB();
+
+               $revQuery = Revision::getQueryInfo( [ 'page' ] );
+               $this->addTables( $revQuery['tables'] );
+               $this->addJoinConds( $revQuery['joins'] );
+               $this->addFields( $revQuery['fields'] );
+
+               $revWhere = ActorMigration::newMigration()->getWhere( $db, 'rev_user', $users );
+               if ( !isset( $revWhere['orconds'][$which] ) ) {
+                       return false;
+               }
+               $this->addWhere( $revWhere['orconds'][$which] );
+
+               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                       $orderUserField = 'rev_actor';
+                       $userField = $this->orderBy === 'actor' ? 'revactor_actor' : 'actor_name';
+               } else {
+                       $orderUserField = $this->orderBy === 'id' ? 'rev_user' : 'rev_user_text';
+                       $userField = $revQuery['fields'][$orderUserField];
+               }
+               if ( $which === 'actor' ) {
+                       $tsField = 'revactor_timestamp';
+                       $idField = 'revactor_rev';
+               } else {
+                       $tsField = 'rev_timestamp';
+                       $idField = 'rev_id';
+               }
 
                // Handle continue parameter
                if ( !is_null( $this->params['continue'] ) ) {
                        $continue = explode( '|', $this->params['continue'] );
-                       $db = $this->getDB();
                        if ( $this->multiUserMode ) {
                                $this->dieContinueUsageIf( count( $continue ) != 4 );
                                $modeFlag = array_shift( $continue );
-                               $this->dieContinueUsageIf( !in_array( $modeFlag, [ 'id', 'name' ] ) );
-                               if ( $this->idMode && $modeFlag === 'name' ) {
-                                       // The users were created since this query started, but we
-                                       // can't go back and change modes now. So just keep on with
-                                       // name mode.
-                                       $this->idMode = false;
-                               }
-                               $this->dieContinueUsageIf( ( $modeFlag === 'id' ) !== $this->idMode );
-                               $userField = $this->idMode ? 'rev_user' : 'rev_user_text';
+                               $this->dieContinueUsageIf( $modeFlag !== $this->orderBy );
                                $encUser = $db->addQuotes( array_shift( $continue ) );
                        } else {
                                $this->dieContinueUsageIf( count( $continue ) != 2 );
@@ -218,21 +479,22 @@ class ApiQueryContributions extends ApiQueryBase {
                                $this->addWhere(
                                        "$userField $op $encUser OR " .
                                        "($userField = $encUser AND " .
-                                       "(rev_timestamp $op $encTS OR " .
-                                       "(rev_timestamp = $encTS AND " .
-                                       "rev_id $op= $encId)))"
+                                       "($tsField $op $encTS OR " .
+                                       "($tsField = $encTS AND " .
+                                       "$idField $op= $encId)))"
                                );
                        } else {
                                $this->addWhere(
-                                       "rev_timestamp $op $encTS OR " .
-                                       "(rev_timestamp = $encTS AND " .
-                                       "rev_id $op= $encId)"
+                                       "$tsField $op $encTS OR " .
+                                       "($tsField = $encTS AND " .
+                                       "$idField $op= $encId)"
                                );
                        }
                }
 
                // Don't include any revisions where we're not supposed to be able to
                // see the username.
+               $user = $this->getUser();
                if ( !$user->isAllowed( 'deletedhistory' ) ) {
                        $bitmask = Revision::DELETED_USER;
                } elseif ( !$user->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
@@ -241,29 +503,20 @@ class ApiQueryContributions extends ApiQueryBase {
                        $bitmask = 0;
                }
                if ( $bitmask ) {
-                       $this->addWhere( $this->getDB()->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
+                       $this->addWhere( $db->bitAnd( 'rev_deleted', $bitmask ) . " != $bitmask" );
                }
 
-               // We only want pages by the specified users.
-               if ( $this->prefixMode ) {
-                       $this->addWhere( 'rev_user_text' .
-                               $this->getDB()->buildLike( $this->userprefix, $this->getDB()->anyString() ) );
-               } elseif ( $this->idMode ) {
-                       $this->addWhereFld( 'rev_user', $this->userids );
-               } else {
-                       $this->addWhereFld( 'rev_user_text', $this->usernames );
-               }
-               // ... and in the specified timeframe.
-               // Ensure the same sort order for rev_user/rev_user_text and rev_timestamp
-               // so our query is indexed
-               if ( $this->multiUserMode ) {
-                       $this->addWhereRange( $this->idMode ? 'rev_user' : 'rev_user_text',
-                               $this->params['dir'], null, null );
+               // Add the user field to ORDER BY if there are multiple users
+               if ( count( $users ) > 1 ) {
+                       $this->addWhereRange( $orderUserField, $this->params['dir'], null, null );
                }
-               $this->addTimestampWhereRange( 'rev_timestamp',
+
+               // Then timestamp
+               $this->addTimestampWhereRange( $tsField,
                        $this->params['dir'], $this->params['start'], $this->params['end'] );
-               // Include in ORDER BY for uniqueness
-               $this->addWhereRange( 'rev_id', $this->params['dir'], null, null );
+
+               // Then rev_id for a total ordering
+               $this->addWhereRange( $idField, $this->params['dir'], null, null );
 
                $this->addWhereFld( 'page_namespace', $this->params['namespace'] );
 
@@ -286,25 +539,12 @@ class ApiQueryContributions extends ApiQueryBase {
                        $this->addWhereIf( 'rev_minor_edit != 0', isset( $show['minor'] ) );
                        $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
                        $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
-                       $this->addWhereIf( 'rev_id != page_latest', isset( $show['!top'] ) );
-                       $this->addWhereIf( 'rev_id = page_latest', isset( $show['top'] ) );
+                       $this->addWhereIf( $idField . ' != page_latest', isset( $show['!top'] ) );
+                       $this->addWhereIf( $idField . ' = page_latest', isset( $show['top'] ) );
                        $this->addWhereIf( 'rev_parent_id != 0', isset( $show['!new'] ) );
                        $this->addWhereIf( 'rev_parent_id = 0', isset( $show['new'] ) );
                }
-               $this->addOption( 'LIMIT', $this->params['limit'] + 1 );
-
-               // Mandatory fields: timestamp allows request continuation
-               // ns+title checks if the user has access rights for this page
-               // user_text is necessary if multiple users were specified
-               $this->addFields( [
-                       'rev_id',
-                       'rev_timestamp',
-                       'page_namespace',
-                       'page_title',
-                       'rev_user',
-                       'rev_user_text',
-                       'rev_deleted'
-               ] );
+               $this->addOption( 'LIMIT', $limit + 1 );
 
                if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ||
                        $this->fld_patrolled
@@ -313,48 +553,25 @@ class ApiQueryContributions extends ApiQueryBase {
                                $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
                        }
 
-                       // Use a redundant join condition on both
-                       // timestamp and ID so we can use the timestamp
-                       // index
-                       $index['recentchanges'] = 'rc_user_text';
-                       if ( isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ) {
-                               // Put the tables in the right order for
-                               // STRAIGHT_JOIN
-                               $tables = [ 'revision', 'recentchanges', 'page' ];
-                               $this->addOption( 'STRAIGHT_JOIN' );
-                               $this->addWhere( 'rc_user_text=rev_user_text' );
-                               $this->addWhere( 'rc_timestamp=rev_timestamp' );
-                               $this->addWhere( 'rc_this_oldid=rev_id' );
-                       } else {
-                               $tables[] = 'recentchanges';
-                               $this->addJoinConds( [ 'recentchanges' => [
-                                       'LEFT JOIN', [
-                                               'rc_user_text=rev_user_text',
-                                               'rc_timestamp=rev_timestamp',
-                                               'rc_this_oldid=rev_id' ] ] ] );
-                       }
+                       $this->addTables( 'recentchanges' );
+                       $this->addJoinConds( [ 'recentchanges' => [
+                               isset( $show['patrolled'] ) || isset( $show['!patrolled'] ) ? 'JOIN' : 'LEFT JOIN',
+                               [
+                                       // This is a crazy hack. recentchanges has no index on rc_this_oldid, so instead of adding
+                                       // one T19237 did a join using rc_user_text and rc_timestamp instead. Now rc_user_text is
+                                       // probably unavailable, so just do rc_timestamp.
+                                       'rc_timestamp = ' . $tsField,
+                                       'rc_this_oldid = ' . $idField,
+                               ]
+                       ] ] );
                }
 
-               $this->addTables( $tables );
-               $this->addFieldsIf( 'rev_page', $this->fld_ids );
-               $this->addFieldsIf( 'page_latest', $this->fld_flags );
-               // $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed?
-               $this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff );
-               $this->addFieldsIf( 'rev_minor_edit', $this->fld_flags );
-               $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff || $this->fld_ids );
                $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled );
 
-               if ( $this->fld_comment || $this->fld_parsedcomment ) {
-                       $commentQuery = $this->commentStore->getJoin( 'rev_comment' );
-                       $this->addTables( $commentQuery['tables'] );
-                       $this->addFields( $commentQuery['fields'] );
-                       $this->addJoinConds( $commentQuery['joins'] );
-               }
-
                if ( $this->fld_tags ) {
                        $this->addTables( 'tag_summary' );
                        $this->addJoinConds(
-                               [ 'tag_summary' => [ 'LEFT JOIN', [ 'rev_id=ts_rev_id' ] ] ]
+                               [ 'tag_summary' => [ 'LEFT JOIN', [ $idField . ' = ts_rev_id' ] ] ]
                        );
                        $this->addFields( 'ts_tags' );
                }
@@ -362,14 +579,12 @@ class ApiQueryContributions extends ApiQueryBase {
                if ( isset( $this->params['tag'] ) ) {
                        $this->addTables( 'change_tag' );
                        $this->addJoinConds(
-                               [ 'change_tag' => [ 'INNER JOIN', [ 'rev_id=ct_rev_id' ] ] ]
+                               [ 'change_tag' => [ 'INNER JOIN', [ $idField . ' = ct_rev_id' ] ] ]
                        );
                        $this->addWhereFld( 'ct_tag', $this->params['tag'] );
                }
 
-               if ( isset( $index ) ) {
-                       $this->addOption( 'USE INDEX', $index );
-               }
+               return true;
        }
 
        /**
@@ -480,10 +695,13 @@ class ApiQueryContributions extends ApiQueryBase {
 
        private function continueStr( $row ) {
                if ( $this->multiUserMode ) {
-                       if ( $this->idMode ) {
-                               return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
-                       } else {
-                               return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
+                       switch ( $this->orderBy ) {
+                               case 'id':
+                                       return "id|$row->rev_user|$row->rev_timestamp|$row->rev_id";
+                               case 'name':
+                                       return "name|$row->rev_user_text|$row->rev_timestamp|$row->rev_id";
+                               case 'actor':
+                                       return "actor|$row->rev_actor|$row->rev_timestamp|$row->rev_id";
                        }
                } else {
                        return "$row->rev_timestamp|$row->rev_id";
index b4b9321..23163c2 100644 (file)
@@ -340,11 +340,15 @@ class ApiStashEdit extends ApiBase {
         * @return string|null TS_MW timestamp or null
         */
        private static function lastEditTime( User $user ) {
-               $time = wfGetDB( DB_REPLICA )->selectField(
-                       'recentchanges',
+               $db = wfGetDB( DB_REPLICA );
+               $actorQuery = ActorMigration::newMigration()->getWhere( $db, 'rc_user', $user, false );
+               $time = $db->selectField(
+                       [ 'recentchanges' ] + $actorQuery['tables'],
                        'MAX(rc_timestamp)',
-                       [ 'rc_user_text' => $user->getName() ],
-                       __METHOD__
+                       [ $actorQuery['conds'] ],
+                       __METHOD__,
+                       [],
+                       $actorQuery['joins']
                );
 
                return wfTimestampOrNull( TS_MW, $time );
index 215e2ff..7e33e42 100644 (file)
@@ -27,7 +27,8 @@
                        "Redredsonia",
                        "Alexey zakharenkov",
                        "Facenapalm",
-                       "Jack who built the house"
+                       "Jack who built the house",
+                       "Mouse21"
                ]
        },
        "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|Документация]]\n* [[mw:Special:MyLanguage/API:FAQ|ЧаВО]]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api Почтовая рассылка]\n* [https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce Новости API]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R Ошибки и запросы]\n</div>\n<strong>Статус:</strong> Все отображаемые на этой странице функции должны работать, однако API находится в статусе активной разработки и может измениться в любой момент. Подпишитесь на [https://lists.wikimedia.org/pipermail/mediawiki-api-announce/ почтовую рассылку mediawiki-api-announce], чтобы быть в курсе обновлений.\n\n<strong>Ошибочные запросы:</strong> Если API получает запрос с ошибкой, вернётся заголовок HTTP с ключом «MediaWiki-API-Error», после чего значение заголовка и код ошибки будут отправлены обратно и установлены в то же значение. Более подробную информацию см. [[mw:Special:MyLanguage/API:Errors_and_warnings|API: Ошибки и предупреждения]].\n\n<p class=\"mw-apisandbox-link\"><strong>Тестирование:</strong> для удобства тестирования API-запросов, см. [[Special:ApiSandbox]].</p>",
        "apihelp-parse-param-disablepp": "Вместо этого используйте <var>$1disablelimitreport</var>.",
        "apihelp-parse-param-disableeditsection": "Опустить ссылки на редактирование разделов из результата парсинга.",
        "apihelp-parse-param-disabletidy": "Не проводить очистку HTML (например, с помощью tidy) результатов парсинга.",
+       "apihelp-parse-param-disablestylededuplication": "Не дедуплицируйте встроенные таблицы стилей в выходе парсера.",
        "apihelp-parse-param-generatexml": "Сгенерировать дерево парсинга XML (требуется модель содержимого <code>$1</code>, замещено <kbd>$2prop=parsetree</kbd>).",
        "apihelp-parse-param-preview": "Проанализировать в режиме препросмотра.",
        "apihelp-parse-param-sectionpreview": "Распарсить в режиме предпросмотра раздела (также активирует режим предпросмотра).",
index 5c75292..cb68571 100644 (file)
@@ -80,6 +80,8 @@ class UserCache {
         * @param string $caller The calling method
         */
        public function doQuery( array $userIds, $options = [], $caller = '' ) {
+               global $wgActorTableSchemaMigrationStage;
+
                $usersToCheck = [];
                $usersToQuery = [];
 
@@ -100,21 +102,34 @@ class UserCache {
                // Lookup basic info for users not yet loaded...
                if ( count( $usersToQuery ) ) {
                        $dbr = wfGetDB( DB_REPLICA );
-                       $table = [ 'user' ];
+                       $tables = [ 'user' ];
                        $conds = [ 'user_id' => $usersToQuery ];
                        $fields = [ 'user_name', 'user_real_name', 'user_registration', 'user_id' ];
+                       $joinConds = [];
+
+                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                               $tables[] = 'actor';
+                               $fields[] = 'actor_id';
+                               $joinConds['actor'] = [
+                                       $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                                       [ 'actor_user = user_id' ]
+                               ];
+                       }
 
                        $comment = __METHOD__;
                        if ( strval( $caller ) !== '' ) {
                                $comment .= "/$caller";
                        }
 
-                       $res = $dbr->select( $table, $fields, $conds, $comment );
+                       $res = $dbr->select( $tables, $fields, $conds, $comment, [], $joinConds );
                        foreach ( $res as $row ) { // load each user into cache
                                $userId = (int)$row->user_id;
                                $this->cache[$userId]['name'] = $row->user_name;
                                $this->cache[$userId]['real_name'] = $row->user_real_name;
                                $this->cache[$userId]['registration'] = $row->user_registration;
+                               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                       $this->cache[$userId]['actor'] = $row->actor_id;
+                               }
                                $usersToCheck[$userId] = $row->user_name;
                        }
                }
index 5b8559e..ac029a2 100644 (file)
@@ -646,6 +646,7 @@ class ChangesList extends ContextSource {
                                        'id' => $rc->mAttribs['rc_this_oldid'],
                                        'user' => $rc->mAttribs['rc_user'],
                                        'user_text' => $rc->mAttribs['rc_user_text'],
+                                       'actor' => isset( $rc->mAttribs['rc_actor'] ) ? $rc->mAttribs['rc_actor'] : null,
                                        'deleted' => $rc->mAttribs['rc_deleted']
                                ] );
                                $s .= ' ' . Linker::generateRollback( $rev, $this->getContext() );
index dfaa398..3dacf6a 100644 (file)
@@ -34,6 +34,7 @@
  *  rc_cur_id       page_id of associated page entry
  *  rc_user         user id who made the entry
  *  rc_user_text    user name who made the entry
+ *  rc_actor        actor id who made the entry
  *  rc_comment      edit summary
  *  rc_this_oldid   rev_id associated with this entry (or zero)
  *  rc_last_oldid   rev_id associated with the entry before this one (or zero)
@@ -210,12 +211,25 @@ class RecentChange {
         * @return array
         */
        public static function selectFields() {
+               global $wgActorTableSchemaMigrationStage;
+
                wfDeprecated( __METHOD__, '1.31' );
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->rc_user or $row->rc_user_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
+
                return [
                        'rc_id',
                        'rc_timestamp',
                        'rc_user',
                        'rc_user_text',
+                       'rc_actor' => 'NULL',
                        'rc_namespace',
                        'rc_title',
                        'rc_minor',
@@ -249,13 +263,12 @@ class RecentChange {
         */
        public static function getQueryInfo() {
                $commentQuery = CommentStore::getStore()->getJoin( 'rc_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
                return [
-                       'tables' => [ 'recentchanges' ] + $commentQuery['tables'],
+                       'tables' => [ 'recentchanges' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                'rc_id',
                                'rc_timestamp',
-                               'rc_user',
-                               'rc_user_text',
                                'rc_namespace',
                                'rc_title',
                                'rc_minor',
@@ -275,8 +288,8 @@ class RecentChange {
                                'rc_log_type',
                                'rc_log_action',
                                'rc_params',
-                       ] + $commentQuery['fields'],
-                       'joins' => $commentQuery['joins'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       'joins' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
        }
 
@@ -314,10 +327,14 @@ class RecentChange {
         */
        public function getPerformer() {
                if ( $this->mPerformer === false ) {
-                       if ( $this->mAttribs['rc_user'] ) {
+                       if ( !empty( $this->mAttribs['rc_actor'] ) ) {
+                               $this->mPerformer = User::newFromActorId( $this->mAttribs['rc_actor'] );
+                       } elseif ( !empty( $this->mAttribs['rc_user'] ) ) {
                                $this->mPerformer = User::newFromId( $this->mAttribs['rc_user'] );
-                       } else {
+                       } elseif ( !empty( $this->mAttribs['rc_user_text'] ) ) {
                                $this->mPerformer = User::newFromName( $this->mAttribs['rc_user_text'], false );
+                       } else {
+                               throw new MWException( 'RecentChange object lacks rc_actor, rc_user, and rc_user_text' );
                        }
                }
 
@@ -368,12 +385,22 @@ class RecentChange {
                        unset( $this->mAttribs['rc_cur_id'] );
                }
 
-               # Convert mAttribs['rc_comment'] for CommentStore
                $row = $this->mAttribs;
+
+               # Convert mAttribs['rc_comment'] for CommentStore
                $comment = $row['rc_comment'];
                unset( $row['rc_comment'], $row['rc_comment_text'], $row['rc_comment_data'] );
                $row += CommentStore::getStore()->insert( $dbw, 'rc_comment', $comment );
 
+               # Convert mAttribs['rc_user'] etc for ActorMigration
+               $user = User::newFromAnyId(
+                       isset( $row['rc_user'] ) ? $row['rc_user'] : null,
+                       isset( $row['rc_user_text'] ) ? $row['rc_user_text'] : null,
+                       isset( $row['rc_actor'] ) ? $row['rc_actor'] : null
+               );
+               unset( $row['rc_user'], $row['rc_user_text'], $row['rc_actor'] );
+               $row += ActorMigration::newMigration()->getInsertValues( $dbw, 'rc_user', $user );
+
                # Don't reuse an existing rc_id for the new row, if one happens to be
                # set for some reason.
                unset( $row['rc_id'] );
@@ -642,6 +669,7 @@ class RecentChange {
                        'rc_cur_id' => $title->getArticleID(),
                        'rc_user' => $user->getId(),
                        'rc_user_text' => $user->getName(),
+                       'rc_actor' => $user->getActorId(),
                        'rc_comment' => &$comment,
                        'rc_comment_text' => &$comment,
                        'rc_comment_data' => null,
@@ -717,6 +745,7 @@ class RecentChange {
                        'rc_cur_id' => $title->getArticleID(),
                        'rc_user' => $user->getId(),
                        'rc_user_text' => $user->getName(),
+                       'rc_actor' => $user->getActorId(),
                        'rc_comment' => &$comment,
                        'rc_comment_text' => &$comment,
                        'rc_comment_data' => null,
@@ -849,6 +878,7 @@ class RecentChange {
                        'rc_cur_id' => $target->getArticleID(),
                        'rc_user' => $user->getId(),
                        'rc_user_text' => $user->getName(),
+                       'rc_actor' => $user->getActorId(),
                        'rc_comment' => &$logComment,
                        'rc_comment_text' => &$logComment,
                        'rc_comment_data' => null,
@@ -934,6 +964,7 @@ class RecentChange {
                        'rc_cur_id' => $pageTitle->getArticleID(),
                        'rc_user' => $user ? $user->getId() : 0,
                        'rc_user_text' => $user ? $user->getName() : '',
+                       'rc_actor' => $user ? $user->getActorId() : null,
                        'rc_comment' => &$comment,
                        'rc_comment_text' => &$comment,
                        'rc_comment_data' => null,
@@ -1002,6 +1033,15 @@ class RecentChange {
                $this->mAttribs['rc_comment'] = &$comment;
                $this->mAttribs['rc_comment_text'] = &$comment;
                $this->mAttribs['rc_comment_data'] = null;
+
+               $user = User::newFromAnyId(
+                       isset( $this->mAttribs['rc_user'] ) ? $this->mAttribs['rc_user'] : null,
+                       isset( $this->mAttribs['rc_user_text'] ) ? $this->mAttribs['rc_user_text'] : null,
+                       isset( $this->mAttribs['rc_actor'] ) ? $this->mAttribs['rc_actor'] : null
+               );
+               $this->mAttribs['rc_user'] = $user->getId();
+               $this->mAttribs['rc_user_text'] = $user->getName();
+               $this->mAttribs['rc_actor'] = $user->getActorId();
        }
 
        /**
@@ -1015,6 +1055,24 @@ class RecentChange {
                        return CommentStore::getStore()
                                ->getComment( 'rc_comment', $this->mAttribs, true )->text;
                }
+
+               if ( $name === 'rc_user' || $name === 'rc_user_text' || $name === 'rc_actor' ) {
+                       $user = User::newFromAnyId(
+                               isset( $this->mAttribs['rc_user'] ) ? $this->mAttribs['rc_user'] : null,
+                               isset( $this->mAttribs['rc_user_text'] ) ? $this->mAttribs['rc_user_text'] : null,
+                               isset( $this->mAttribs['rc_actor'] ) ? $this->mAttribs['rc_actor'] : null
+                       );
+                       if ( $name === 'rc_user' ) {
+                               return $user->getId();
+                       }
+                       if ( $name === 'rc_user_text' ) {
+                               return $user->getName();
+                       }
+                       if ( $name === 'rc_actor' ) {
+                               return $user->getActorId();
+                       }
+               }
+
                return isset( $this->mAttribs[$name] ) ? $this->mAttribs[$name] : null;
        }
 
index b78efaf..a248c6e 100644 (file)
@@ -44,6 +44,10 @@ class ChangeTagsLogItem extends RevisionItemBase {
                return 'log_user_text';
        }
 
+       public function getAuthorActorField() {
+               return 'log_actor';
+       }
+
        public function canView() {
                return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED, $this->list->getUser() );
        }
index edfc81c..eab3afb 100644 (file)
@@ -986,13 +986,17 @@ abstract class ContentHandler {
 
                // Find out if there was only one contributor
                // Only scan the last 20 revisions
-               $res = $dbr->select( 'revision', 'rev_user_text',
+               $revQuery = Revision::getQueryInfo();
+               $res = $dbr->select(
+                       $revQuery['tables'],
+                       [ 'rev_user_text' => $revQuery['fields']['rev_user_text'] ],
                        [
                                'rev_page' => $title->getArticleID(),
                                $dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
                        ],
                        __METHOD__,
-                       [ 'LIMIT' => 20 ]
+                       [ 'LIMIT' => 20 ],
+                       $revQuery['joins']
                );
 
                if ( $res === false ) {
index 156e315..225a36c 100644 (file)
@@ -1179,13 +1179,16 @@ class DatabaseOracle extends Database {
        }
 
        public function delete( $table, $conds, $fname = __METHOD__ ) {
+               global $wgActorTableSchemaMigrationStage;
+
                if ( is_array( $conds ) ) {
                        $conds = $this->wrapConditionsForWhere( $table, $conds );
                }
                // a hack for deleting pages, users and images (which have non-nullable FKs)
                // all deletions on these tables have transactions so final failure rollbacks these updates
+               // @todo: Normalize the schema to match MySQL, no special FKs and such
                $table = $this->tableName( $table );
-               if ( $table == $this->tableName( 'user' ) ) {
+               if ( $table == $this->tableName( 'user' ) && $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
                        $this->update( 'archive', [ 'ar_user' => 0 ],
                                [ 'ar_user' => $conds['user_id'] ], $fname );
                        $this->update( 'ipblocks', [ 'ipb_user' => 0 ],
index 2f882b8..79aab7d 100644 (file)
@@ -148,18 +148,21 @@ class SiteStatsUpdate implements DeferrableUpdate, MergeableUpdate {
                $dbr = $services->getDBLoadBalancer()->getConnection( DB_REPLICA, 'vslow' );
                # Get non-bot users than did some recent action other than making accounts.
                # If account creation is included, the number gets inflated ~20+ fold on enwiki.
+               $rcQuery = RecentChange::getQueryInfo();
                $activeUsers = $dbr->selectField(
-                       'recentchanges',
-                       'COUNT( DISTINCT rc_user_text )',
+                       $rcQuery['tables'],
+                       'COUNT( DISTINCT ' . $rcQuery['fields']['rc_user_text'] . ' )',
                        [
                                'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Exclude external (Wikidata)
-                               'rc_user != 0',
+                               ActorMigration::newMigration()->isNotAnon( $rcQuery['fields']['rc_user'] ),
                                'rc_bot' => 0,
                                'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
                                'rc_timestamp >= ' . $dbr->addQuotes(
                                        $dbr->timestamp( time() - $config->get( 'ActiveUserDays' ) * 24 * 3600 ) ),
                        ],
-                       __METHOD__
+                       __METHOD__,
+                       [],
+                       $rcQuery['joins']
                );
                $dbw->update(
                        'site_stats',
diff --git a/includes/exception/CannotCreateActorException.php b/includes/exception/CannotCreateActorException.php
new file mode 100644 (file)
index 0000000..7c7ccfc
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+/**
+ * Exception thrown when some operation failed
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ *
+ * @since 1.31
+ */
+
+/**
+ * Exception thrown when an actor can't be created.
+ */
+class CannotCreateActorException extends RuntimeException {
+}
index 6ce55ea..6c7a449 100644 (file)
@@ -227,15 +227,20 @@ class WikiExporter {
                $this->author_list = "<contributors>";
                // rev_deleted
 
+               $revQuery = Revision::getQueryInfo( [ 'page' ] );
                $res = $this->db->select(
-                       [ 'page', 'revision' ],
-                       [ 'DISTINCT rev_user_text', 'rev_user' ],
+                       $revQuery['tables'],
+                       [
+                               'rev_user_text' => $revQuery['fields']['rev_user_text'],
+                               'rev_user' => $revQuery['fields']['rev_user'],
+                       ],
                        [
                                $this->db->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0',
                                $cond,
-                               'page_id = rev_id',
                        ],
-                       __METHOD__
+                       __METHOD__,
+                       [ 'DISTINCT' ],
+                       $revQuery['joins']
                );
 
                foreach ( $res as $row ) {
@@ -279,14 +284,18 @@ class WikiExporter {
                        $result = null; // Assuring $result is not undefined, if exception occurs early
 
                        $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+                       $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
 
                        try {
-                               $result = $this->db->select( [ 'logging', 'user' ] + $commentQuery['tables'],
-                                       [ "{$logging}.*", 'user_name' ] + $commentQuery['fields'], // grab the user name
+                               $result = $this->db->select(
+                                       array_merge( [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] ),
+                                       [ "{$logging}.*", 'user_name' ] + $commentQuery['fields'] + $actorQuery['fields'],
                                        $where,
                                        __METHOD__,
                                        [ 'ORDER BY' => 'log_id', 'USE INDEX' => [ 'logging' => 'PRIMARY' ] ],
-                                       [ 'user' => [ 'JOIN', 'user_id = log_user' ] ] + $commentQuery['joins']
+                                       [
+                                               'user' => [ 'JOIN', 'user_id = ' . $actorQuery['fields']['log_user'] ]
+                                       ] + $commentQuery['joins'] + $actorQuery['joins']
                                );
                                $this->outputLogStream( $result );
                                if ( $this->buffer == self::STREAM ) {
@@ -321,13 +330,29 @@ class WikiExporter {
                        }
                # For page dumps...
                } else {
-                       $tables = [ 'page', 'revision' ];
+                       $revOpts = [ 'page' ];
+                       if ( $this->text != self::STUB ) {
+                               $revOpts[] = 'text';
+                       }
+                       $revQuery = Revision::getQueryInfo( $revOpts );
+
+                       // We want page primary rather than revision
+                       $tables = array_merge( [ 'page' ], array_diff( $revQuery['tables'], [ 'page' ] ) );
+                       $join = $revQuery['joins'] + [
+                               'revision' => $revQuery['joins']['page']
+                       ];
+                       unset( $join['page'] );
+
+                       $fields = array_merge( $revQuery['fields'], [ 'page_restrictions' ] );
+
+                       $conds = [];
+                       if ( $cond !== '' ) {
+                               $conds[] = $cond;
+                       }
                        $opts = [ 'ORDER BY' => 'page_id ASC' ];
                        $opts['USE INDEX'] = [];
-                       $join = [];
                        if ( is_array( $this->history ) ) {
                                # Time offset/limit for all pages/history...
-                               $revJoin = 'page_id=rev_page';
                                # Set time order
                                if ( $this->history['dir'] == 'asc' ) {
                                        $op = '>';
@@ -338,10 +363,9 @@ class WikiExporter {
                                }
                                # Set offset
                                if ( !empty( $this->history['offset'] ) ) {
-                                       $revJoin .= " AND rev_timestamp $op " .
+                                       $conds[] = "rev_timestamp $op " .
                                                $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) );
                                }
-                               $join['revision'] = [ 'INNER JOIN', $revJoin ];
                                # Set query limit
                                if ( !empty( $this->history['limit'] ) ) {
                                        $opts['LIMIT'] = intval( $this->history['limit'] );
@@ -350,13 +374,11 @@ class WikiExporter {
                                # Full history dumps...
                                # query optimization for history stub dumps
                                if ( $this->text == self::STUB && $orderRevs ) {
-                                       $tables = [ 'revision', 'page' ];
-                                       $opts[] = 'STRAIGHT_JOIN';
+                                       $tables = $revQuery['tables'];
                                        $opts['ORDER BY'] = [ 'rev_page ASC', 'rev_id ASC' ];
                                        $opts['USE INDEX']['revision'] = 'rev_page_id';
+                                       unset( $join['revision'] );
                                        $join['page'] = [ 'INNER JOIN', 'rev_page=page_id' ];
-                               } else {
-                                       $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page' ];
                                }
                        } elseif ( $this->history & self::CURRENT ) {
                                # Latest revision dumps...
@@ -374,22 +396,11 @@ class WikiExporter {
                                }
                        } elseif ( $this->history & self::RANGE ) {
                                # Dump of revisions within a specified range
-                               $join['revision'] = [ 'INNER JOIN', 'page_id=rev_page' ];
                                $opts['ORDER BY'] = [ 'rev_page ASC', 'rev_id ASC' ];
                        } else {
                                # Unknown history specification parameter?
                                throw new MWException( __METHOD__ . " given invalid history dump type." );
                        }
-                       # Query optimization hacks
-                       if ( $cond == '' ) {
-                               $opts[] = 'STRAIGHT_JOIN';
-                               $opts['USE INDEX']['page'] = 'PRIMARY';
-                       }
-                       # Build text join options
-                       if ( $this->text != self::STUB ) { // 1-pass
-                               $tables[] = 'text';
-                               $join['text'] = [ 'INNER JOIN', 'rev_text_id=old_id' ];
-                       }
 
                        if ( $this->buffer == self::STREAM ) {
                                $prev = $this->db->bufferResults( false );
@@ -399,16 +410,14 @@ class WikiExporter {
                                Hooks::run( 'ModifyExportQuery',
                                                [ $this->db, &$tables, &$cond, &$opts, &$join ] );
 
-                               $commentQuery = CommentStore::getStore()->getJoin( 'rev_comment' );
-
                                # Do the query!
                                $result = $this->db->select(
-                                       $tables + $commentQuery['tables'],
-                                       [ '*' ] + $commentQuery['fields'],
-                                       $cond,
+                                       $tables,
+                                       $fields,
+                                       $conds,
                                        __METHOD__,
                                        $opts,
-                                       $join + $commentQuery['joins']
+                                       $join
                                );
                                # Output dump results
                                $this->outputPageStream( $result );
index 3beab29..de7d1a4 100644 (file)
@@ -92,6 +92,7 @@ class ExternalStore {
         * @param array $urls The URLs of the text to get
         * @return array Map from url to its data.  Data is either string when found
         *     or false on failure.
+        * @throws MWException
         */
        public static function batchFetchFromURLs( array $urls ) {
                $batches = [];
@@ -157,7 +158,7 @@ class ExternalStore {
         * provided by $wgDefaultExternalStore.
         *
         * @param string $data
-        * @param array $params Associative array of ExternalStoreMedium parameters
+        * @param array $params Map of ExternalStoreMedium::__construct context parameters
         * @return string|bool The URL of the stored data item, or false on error
         * @throws MWException
         */
@@ -175,7 +176,7 @@ class ExternalStore {
         *
         * @param array $tryStores Refer to $wgDefaultExternalStore
         * @param string $data
-        * @param array $params Associative array of ExternalStoreMedium parameters
+        * @param array $params Map of ExternalStoreMedium::__construct context parameters
         * @return string|bool The URL of the stored data item, or false on error
         * @throws MWException
         */
@@ -190,19 +191,25 @@ class ExternalStore {
                        if ( $store === false ) {
                                throw new MWException( "Invalid external storage protocol - $storeUrl" );
                        }
+
                        try {
-                               $url = $store->store( $path, $data ); // Try to save the object
+                               if ( $store->isReadOnly( $path ) ) {
+                                       $msg = 'read only';
+                               } else {
+                                       $url = $store->store( $path, $data );
+                                       if ( strlen( $url ) ) {
+                                               return $url; // a store accepted the write; done!
+                                       }
+                                       $msg = 'operation failed';
+                               }
                        } catch ( Exception $error ) {
-                               $url = false;
-                       }
-                       if ( strlen( $url ) ) {
-                               return $url; // Done!
-                       } else {
-                               unset( $tryStores[$index] ); // Don't try this one again!
-                               $tryStores = array_values( $tryStores ); // Must have consecutive keys
-                               wfDebugLog( 'ExternalStorage',
-                                       "Unable to store text to external storage $storeUrl" );
+                               $msg = 'caught exception';
                        }
+
+                       unset( $tryStores[$index] ); // Don't try this one again!
+                       $tryStores = array_values( $tryStores ); // Must have consecutive keys
+                       wfDebugLog( 'ExternalStorage',
+                               "Unable to store text to external storage $storeUrl ($msg)" );
                }
                // All stores failed
                if ( $error ) {
@@ -212,6 +219,29 @@ class ExternalStore {
                }
        }
 
+       /**
+        * @return bool Whether all the default insertion stores are marked as read-only
+        * @since 1.31
+        */
+       public static function defaultStoresAreReadOnly() {
+               global $wgDefaultExternalStore;
+
+               $tryStores = (array)$wgDefaultExternalStore;
+               if ( !$tryStores ) {
+                       return false; // no stores exists which can be "read only"
+               }
+
+               foreach ( $tryStores as $storeUrl ) {
+                       list( $proto, $path ) = explode( '://', $storeUrl, 2 );
+                       $store = self::getStoreObject( $proto, [] );
+                       if ( !$store->isReadOnly( $path ) ) {
+                               return false; // at least one store is not read-only
+                       }
+               }
+
+               return true; // all stores are read-only
+       }
+
        /**
         * @param string $data
         * @param string $wiki
index e5d36e1..b43bd21 100644 (file)
@@ -103,6 +103,10 @@ class ExternalStoreDB extends ExternalStoreMedium {
                return "DB://$location/$id";
        }
 
+       public function isReadOnly( $location ) {
+               return ( $this->getLoadBalancer( $location )->getReadOnlyReason() !== false );
+       }
+
        /**
         * Get a LoadBalancer for the specified cluster
         *
index 8e1e49f..3d812c9 100644 (file)
  * @ingroup ExternalStorage
  */
 class ExternalStoreHttp extends ExternalStoreMedium {
-       /**
-        * @see ExternalStoreMedium::fetchFromURL()
-        * @param string $url
-        * @return string|bool
-        * @throws MWException
-        */
        public function fetchFromURL( $url ) {
                return Http::get( $url, [], __METHOD__ );
        }
 
-       /**
-        * @see ExternalStoreMedium::store()
-        * @param string $cluster
-        * @param string $data
-        * @return string|bool
-        * @throws MWException
-        */
-       public function store( $cluster, $data ) {
+       public function store( $location, $data ) {
                throw new MWException( "ExternalStoreHttp is read-only and does not support store()." );
        }
+
+       public function isReadOnly( $location ) {
+               return true;
+       }
 }
index 6cfa083..da7752b 100644 (file)
@@ -32,7 +32,8 @@ abstract class ExternalStoreMedium {
        protected $params = [];
 
        /**
-        * @param array $params Options
+        * @param array $params Usage context options:
+        *   - wiki: the domain ID of the wiki this is being used for [optional]
         */
        public function __construct( array $params = [] ) {
                $this->params = $params;
@@ -76,4 +77,15 @@ abstract class ExternalStoreMedium {
         * @throws MWException
         */
        abstract public function store( $location, $data );
+
+       /**
+        * Check if a given location is read-only
+        *
+        * @param string $location The location name
+        * @return bool Whether this location is read-only
+        * @since 1.31
+        */
+       public function isReadOnly( $location ) {
+               return false;
+       }
 }
index 5395f56..0c6d022 100644 (file)
@@ -73,13 +73,6 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
                return $blobs;
        }
 
-       /**
-        * @see ExternalStoreMedium::store()
-        * @param string $backend
-        * @param string $data
-        * @return string|bool
-        * @throws MWException
-        */
        public function store( $backend, $data ) {
                $be = FileBackendGroup::singleton()->get( $backend );
                if ( $be instanceof FileBackend ) {
@@ -103,4 +96,10 @@ class ExternalStoreMwstore extends ExternalStoreMedium {
 
                return false;
        }
+
+       public function isReadOnly( $backend ) {
+               $be = FileBackendGroup::singleton()->get( $backend );
+
+               return $be ? $be->isReadOnly() : false;
+       }
 }
index 6577ab6..982fea4 100644 (file)
@@ -63,12 +63,9 @@ class ArchivedFile {
        /** @var string Upload description */
        private $description;
 
-       /** @var int User ID of uploader */
+       /** @var User|null Uploader */
        private $user;
 
-       /** @var string User name of uploader */
-       private $user_text;
-
        /** @var string Time of upload */
        private $timestamp;
 
@@ -116,8 +113,7 @@ class ArchivedFile {
                $this->mime = "unknown/unknown";
                $this->media_type = '';
                $this->description = '';
-               $this->user = 0;
-               $this->user_text = '';
+               $this->user = null;
                $this->timestamp = null;
                $this->deleted = 0;
                $this->dataLoaded = false;
@@ -221,6 +217,18 @@ class ArchivedFile {
         * @return array
         */
        static function selectFields() {
+               global $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->fa_user or $row->fa_user_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
+
                wfDeprecated( __METHOD__, '1.31' );
                return [
                        'fa_id',
@@ -238,6 +246,7 @@ class ArchivedFile {
                        'fa_minor_mime',
                        'fa_user',
                        'fa_user_text',
+                       'fa_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'fa_actor' : null,
                        'fa_timestamp',
                        'fa_deleted',
                        'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
@@ -256,8 +265,9 @@ class ArchivedFile {
         */
        public static function getQueryInfo() {
                $commentQuery = CommentStore::getStore()->getJoin( 'fa_description' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'fa_user' );
                return [
-                       'tables' => [ 'filearchive' ] + $commentQuery['tables'],
+                       'tables' => [ 'filearchive' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                'fa_id',
                                'fa_name',
@@ -272,14 +282,12 @@ class ArchivedFile {
                                'fa_media_type',
                                'fa_major_mime',
                                'fa_minor_mime',
-                               'fa_user',
-                               'fa_user_text',
                                'fa_timestamp',
                                'fa_deleted',
                                'fa_deleted_timestamp', /* Used by LocalFileRestoreBatch */
                                'fa_sha1',
-                       ] + $commentQuery['fields'],
-                       'joins' => $commentQuery['joins'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       'joins' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
        }
 
@@ -305,8 +313,7 @@ class ArchivedFile {
                $this->description = CommentStore::getStore()
                        // Legacy because $row may have come from self::selectFields()
                        ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'fa_description', $row )->text;
-               $this->user = $row->fa_user;
-               $this->user_text = $row->fa_user_text;
+               $this->user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
                $this->timestamp = $row->fa_timestamp;
                $this->deleted = $row->fa_deleted;
                if ( isset( $row->fa_sha1 ) ) {
@@ -519,17 +526,20 @@ class ArchivedFile {
         * @note Prior to MediaWiki 1.23, this method always
         *   returned the user id, and was inconsistent with
         *   the rest of the file classes.
-        * @param string $type 'text' or 'id'
-        * @return int|string
+        * @param string $type 'text', 'id', or 'object'
+        * @return int|string|User|null
         * @throws MWException
+        * @since 1.31 added 'object'
         */
        public function getUser( $type = 'text' ) {
                $this->load();
 
-               if ( $type == 'text' ) {
-                       return $this->user_text;
-               } elseif ( $type == 'id' ) {
-                       return (int)$this->user;
+               if ( $type === 'object' ) {
+                       return $this->user;
+               } elseif ( $type === 'text' ) {
+                       return $this->user ? $this->user->getName() : '';
+               } elseif ( $type === 'id' ) {
+                       return $this->user ? $this->user->getId() : 0;
                }
 
                throw new MWException( "Unknown type '$type'." );
@@ -555,9 +565,7 @@ class ArchivedFile {
         * @return int
         */
        public function getRawUser() {
-               $this->load();
-
-               return $this->user;
+               return $this->getUser( 'id' );
        }
 
        /**
@@ -566,9 +574,7 @@ class ArchivedFile {
         * @return string
         */
        public function getRawUserText() {
-               $this->load();
-
-               return $this->user_text;
+               return $this->getUser( 'text' );
        }
 
        /**
index 7cf2749..ec4a5fb 100644 (file)
@@ -24,6 +24,7 @@
 use MediaWiki\Logger\LoggerFactory;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\IDatabase;
+use MediaWiki\MediaWikiServices;
 
 /**
  * Class to represent a local file in the wiki's own database
@@ -101,12 +102,9 @@ class LocalFile extends File {
        /** @var string Upload timestamp */
        private $timestamp;
 
-       /** @var int User ID of uploader */
+       /** @var User Uploader */
        private $user;
 
-       /** @var string User name of uploader */
-       private $user_text;
-
        /** @var string Description of current revision of the file */
        private $description;
 
@@ -200,7 +198,19 @@ class LocalFile extends File {
         * @return array
         */
        static function selectFields() {
+               global $wgActorTableSchemaMigrationStage;
+
                wfDeprecated( __METHOD__, '1.31' );
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->img_user or $row->img_user_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
+
                return [
                        'img_name',
                        'img_size',
@@ -213,6 +223,7 @@ class LocalFile extends File {
                        'img_minor_mime',
                        'img_user',
                        'img_user_text',
+                       'img_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'img_actor' : null,
                        'img_timestamp',
                        'img_sha1',
                ] + CommentStore::getStore()->getFields( 'img_description' );
@@ -231,8 +242,9 @@ class LocalFile extends File {
         */
        public static function getQueryInfo( array $options = [] ) {
                $commentQuery = CommentStore::getStore()->getJoin( 'img_description' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'img_user' );
                $ret = [
-                       'tables' => [ 'image' ] + $commentQuery['tables'],
+                       'tables' => [ 'image' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                'img_name',
                                'img_size',
@@ -243,12 +255,10 @@ class LocalFile extends File {
                                'img_media_type',
                                'img_major_mime',
                                'img_minor_mime',
-                               'img_user',
-                               'img_user_text',
                                'img_timestamp',
                                'img_sha1',
-                       ] + $commentQuery['fields'],
-                       'joins' => $commentQuery['joins'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       'joins' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
 
                if ( in_array( 'omit-nonlazy', $options, true ) ) {
@@ -329,6 +339,10 @@ class LocalFile extends File {
                                                $cacheVal[$field] = $this->$field;
                                        }
                                }
+                               $cacheVal['user'] = $this->user ? $this->user->getId() : 0;
+                               $cacheVal['user_text'] = $this->user ? $this->user->getName() : '';
+                               $cacheVal['actor'] = $this->user ? $this->user->getActorId() : null;
+
                                // Strip off excessive entries from the subset of fields that can become large.
                                // If the cache value gets to large it will not fit in memcached and nothing will
                                // get cached at all, causing master queries for any file access.
@@ -406,8 +420,7 @@ class LocalFile extends File {
                // and self::loadFromCache() for the caching, and self::setProps() for
                // populating the object from an array of data.
                return [ 'size', 'width', 'height', 'bits', 'media_type',
-                       'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'user',
-                       'user_text', 'description' ];
+                       'major_mime', 'minor_mime', 'metadata', 'timestamp', 'sha1', 'description' ];
        }
 
        /**
@@ -569,6 +582,13 @@ class LocalFile extends File {
                $decoded['description'] = CommentStore::getStore()
                        ->getComment( 'description', (object)$decoded )->text;
 
+               $decoded['user'] = User::newFromAnyId(
+                       isset( $decoded['user'] ) ? $decoded['user'] : null,
+                       isset( $decoded['user_text'] ) ? $decoded['user_text'] : null,
+                       isset( $decoded['actor'] ) ? $decoded['actor'] : null
+               );
+               unset( $decoded['user_text'], $decoded['actor'] );
+
                $decoded['timestamp'] = wfTimestamp( TS_MW, $decoded['timestamp'] );
 
                $decoded['metadata'] = $this->repo->getReplicaDB()->decodeBlob( $decoded['metadata'] );
@@ -750,6 +770,14 @@ class LocalFile extends File {
                        }
                }
 
+               if ( isset( $info['user'] ) || isset( $info['user_text'] ) || isset( $info['actor'] ) ) {
+                       $this->user = User::newFromAnyId(
+                               isset( $info['user'] ) ? $info['user'] : null,
+                               isset( $info['user_text'] ) ? $info['user_text'] : null,
+                               isset( $info['actor'] ) ? $info['actor'] : null
+                       );
+               }
+
                // Fix up mime fields
                if ( isset( $info['major_mime'] ) ) {
                        $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
@@ -844,19 +872,24 @@ class LocalFile extends File {
        }
 
        /**
-        * Returns ID or name of user who uploaded the file
+        * Returns user who uploaded the file
         *
-        * @param string $type 'text' or 'id'
-        * @return int|string
+        * @param string $type 'text', 'id', or 'object'
+        * @return int|string|User
+        * @since 1.31 Added 'object'
         */
        function getUser( $type = 'text' ) {
                $this->load();
 
-               if ( $type == 'text' ) {
-                       return $this->user_text;
-               } else { // id
-                       return (int)$this->user;
+               if ( $type === 'object' ) {
+                       return $this->user;
+               } elseif ( $type === 'text' ) {
+                       return $this->user->getName();
+               } elseif ( $type === 'id' ) {
+                       return $this->user->getId();
                }
+
+               throw new MWException( "Unknown type '$type'." );
        }
 
        /**
@@ -1275,6 +1308,10 @@ class LocalFile extends File {
        ) {
                if ( $this->getRepo()->getReadOnlyReason() !== false ) {
                        return $this->readOnlyFatalStatus();
+               } elseif ( MediaWikiServices::getInstance()->getRevisionStore()->isReadOnly() ) {
+                       // Check this in advance to avoid writing to FileBackend and the file tables,
+                       // only to fail on insert the revision due to the text store being unavailable.
+                       return $this->readOnlyFatalStatus();
                }
 
                $srcPath = ( $src instanceof FSFile ) ? $src->getPath() : $src;
@@ -1387,7 +1424,7 @@ class LocalFile extends File {
        function recordUpload2(
                $oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = []
        ) {
-               global $wgCommentTableSchemaMigrationStage;
+               global $wgCommentTableSchemaMigrationStage, $wgActorTableSchemaMigrationStage;
 
                if ( is_null( $user ) ) {
                        global $wgUser;
@@ -1409,6 +1446,7 @@ class LocalFile extends File {
                $props['description'] = $comment;
                $props['user'] = $user->getId();
                $props['user_text'] = $user->getName();
+               $props['actor'] = $user->getActorId( $dbw );
                $props['timestamp'] = wfTimestamp( TS_MW, $timestamp ); // DB -> TS_MW
                $this->setProps( $props );
 
@@ -1427,6 +1465,8 @@ class LocalFile extends File {
                $commentStore = CommentStore::getStore();
                list( $commentFields, $commentCallback ) =
                        $commentStore->insertWithTempTable( $dbw, 'img_description', $comment );
+               $actorMigration = ActorMigration::newMigration();
+               $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
                $dbw->insert( 'image',
                        [
                                'img_name' => $this->getName(),
@@ -1438,11 +1478,9 @@ class LocalFile extends File {
                                'img_major_mime' => $this->major_mime,
                                'img_minor_mime' => $this->minor_mime,
                                'img_timestamp' => $timestamp,
-                               'img_user' => $user->getId(),
-                               'img_user_text' => $user->getName(),
                                'img_metadata' => $dbw->encodeBlob( $this->metadata ),
                                'img_sha1' => $this->sha1
-                       ] + $commentFields,
+                       ] + $commentFields + $actorFields,
                        __METHOD__,
                        'IGNORE'
                );
@@ -1485,8 +1523,6 @@ class LocalFile extends File {
                                'oi_height' => 'img_height',
                                'oi_bits' => 'img_bits',
                                'oi_timestamp' => 'img_timestamp',
-                               'oi_user' => 'img_user',
-                               'oi_user_text' => 'img_user_text',
                                'oi_metadata' => 'img_metadata',
                                'oi_media_type' => 'img_media_type',
                                'oi_major_mime' => 'img_major_mime',
@@ -1529,6 +1565,37 @@ class LocalFile extends File {
                                }
                        }
 
+                       if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               $fields['oi_user'] = 'img_user';
+                               $fields['oi_user_text'] = 'img_user_text';
+                       }
+                       if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               $fields['oi_actor'] = 'img_actor';
+                       }
+
+                       if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
+                               $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+                       ) {
+                               // Upgrade any rows that are still old-style. Otherwise an upgrade
+                               // might be missed if a deletion happens while the migration script
+                               // is running.
+                               $res = $dbw->select(
+                                       [ 'image' ],
+                                       [ 'img_name', 'img_user', 'img_user_text' ],
+                                       [ 'img_name' => $this->getName(), 'img_actor' => 0 ],
+                                       __METHOD__
+                               );
+                               foreach ( $res as $row ) {
+                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
+                                       $dbw->update(
+                                               'image',
+                                               [ 'img_actor' => $actorId ],
+                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
+                                               __METHOD__
+                                       );
+                               }
+                       }
+
                        # (T36993) Note: $oldver can be empty here, if the previous
                        # version of the file was broken. Allow registration of the new
                        # version to continue anyway, because that's better than having
@@ -1549,11 +1616,9 @@ class LocalFile extends File {
                                        'img_major_mime' => $this->major_mime,
                                        'img_minor_mime' => $this->minor_mime,
                                        'img_timestamp' => $timestamp,
-                                       'img_user' => $user->getId(),
-                                       'img_user_text' => $user->getName(),
                                        'img_metadata' => $dbw->encodeBlob( $this->metadata ),
                                        'img_sha1' => $this->sha1
-                               ] + $commentFields,
+                               ] + $commentFields + $actorFields,
                                [ 'img_name' => $this->getName() ],
                                __METHOD__
                        );
@@ -2400,12 +2465,13 @@ class LocalFileDeleteBatch {
        }
 
        protected function doDBInserts() {
-               global $wgCommentTableSchemaMigrationStage;
+               global $wgCommentTableSchemaMigrationStage, $wgActorTableSchemaMigrationStage;
 
                $now = time();
                $dbw = $this->file->repo->getMasterDB();
 
                $commentStore = CommentStore::getStore();
+               $actorMigration = ActorMigration::newMigration();
 
                $encTimestamp = $dbw->addQuotes( $dbw->timestamp( $now ) );
                $encUserId = $dbw->addQuotes( $this->user->getId() );
@@ -2444,8 +2510,6 @@ class LocalFileDeleteBatch {
                                'fa_media_type' => 'img_media_type',
                                'fa_major_mime' => 'img_major_mime',
                                'fa_minor_mime' => 'img_minor_mime',
-                               'fa_user' => 'img_user',
-                               'fa_user_text' => 'img_user_text',
                                'fa_timestamp' => 'img_timestamp',
                                'fa_sha1' => 'img_sha1'
                        ];
@@ -2490,6 +2554,37 @@ class LocalFileDeleteBatch {
                                }
                        }
 
+                       if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                               $fields['fa_user'] = 'img_user';
+                               $fields['fa_user_text'] = 'img_user_text';
+                       }
+                       if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                               $fields['fa_actor'] = 'img_actor';
+                       }
+
+                       if ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
+                               $wgActorTableSchemaMigrationStage !== MIGRATION_NEW
+                       ) {
+                               // Upgrade any rows that are still old-style. Otherwise an upgrade
+                               // might be missed if a deletion happens while the migration script
+                               // is running.
+                               $res = $dbw->select(
+                                       [ 'image' ],
+                                       [ 'img_name', 'img_user', 'img_user_text' ],
+                                       [ 'img_name' => $this->file->getName(), 'img_actor' => 0 ],
+                                       __METHOD__
+                               );
+                               foreach ( $res as $row ) {
+                                       $actorId = User::newFromAnyId( $row->img_user, $row->img_user_text, null )->getActorId( $dbw );
+                                       $dbw->update(
+                                               'image',
+                                               [ 'img_actor' => $actorId ],
+                                               [ 'img_name' => $row->img_name, 'img_actor' => 0 ],
+                                               __METHOD__
+                                       );
+                               }
+                       }
+
                        $dbw->insertSelect( 'filearchive', $tables, $fields,
                                [ 'img_name' => $this->file->getName() ], __METHOD__, [], [], $joins );
                }
@@ -2512,6 +2607,7 @@ class LocalFileDeleteBatch {
                                $reason = $commentStore->createComment( $dbw, $this->reason );
                                foreach ( $res as $row ) {
                                        $comment = $commentStore->getComment( 'oi_description', $row );
+                                       $user = User::newFromAnyId( $row->oi_user, $row->oi_user_text, $row->oi_actor );
                                        $rowsInsert[] = [
                                                // Deletion-specific fields
                                                'fa_storage_group' => 'deleted',
@@ -2532,12 +2628,11 @@ class LocalFileDeleteBatch {
                                                'fa_media_type' => $row->oi_media_type,
                                                'fa_major_mime' => $row->oi_major_mime,
                                                'fa_minor_mime' => $row->oi_minor_mime,
-                                               'fa_user' => $row->oi_user,
-                                               'fa_user_text' => $row->oi_user_text,
                                                'fa_timestamp' => $row->oi_timestamp,
                                                'fa_sha1' => $row->oi_sha1
                                        ] + $commentStore->insert( $dbw, 'fa_deleted_reason', $reason )
-                                       + $commentStore->insert( $dbw, 'fa_description', $comment );
+                                       + $commentStore->insert( $dbw, 'fa_description', $comment )
+                                       + $actorMigration->getInsertValues( $dbw, 'fa_user', $user );
                                }
                        }
 
@@ -2736,6 +2831,7 @@ class LocalFileRestoreBatch {
                $dbw = $this->file->repo->getMasterDB();
 
                $commentStore = CommentStore::getStore();
+               $actorMigration = ActorMigration::newMigration();
 
                $status = $this->file->repo->newGood();
 
@@ -2824,11 +2920,13 @@ class LocalFileRestoreBatch {
                        }
 
                        $comment = $commentStore->getComment( 'fa_description', $row );
+                       $user = User::newFromAnyId( $row->fa_user, $row->fa_user_text, $row->fa_actor );
                        if ( $first && !$exists ) {
                                // This revision will be published as the new current version
                                $destRel = $this->file->getRel();
                                list( $commentFields, $commentCallback ) =
                                        $commentStore->insertWithTempTable( $dbw, 'img_description', $comment );
+                               $actorFields = $actorMigration->getInsertValues( $dbw, 'img_user', $user );
                                $insertCurrent = [
                                        'img_name' => $row->fa_name,
                                        'img_size' => $row->fa_size,
@@ -2839,11 +2937,9 @@ class LocalFileRestoreBatch {
                                        'img_media_type' => $props['media_type'],
                                        'img_major_mime' => $props['major_mime'],
                                        'img_minor_mime' => $props['minor_mime'],
-                                       'img_user' => $row->fa_user,
-                                       'img_user_text' => $row->fa_user_text,
                                        'img_timestamp' => $row->fa_timestamp,
                                        'img_sha1' => $sha1
-                               ] + $commentFields;
+                               ] + $commentFields + $actorFields;
 
                                // The live (current) version cannot be hidden!
                                if ( !$this->unsuppress && $row->fa_deleted ) {
@@ -2875,8 +2971,6 @@ class LocalFileRestoreBatch {
                                        'oi_width' => $row->fa_width,
                                        'oi_height' => $row->fa_height,
                                        'oi_bits' => $row->fa_bits,
-                                       'oi_user' => $row->fa_user,
-                                       'oi_user_text' => $row->fa_user_text,
                                        'oi_timestamp' => $row->fa_timestamp,
                                        'oi_metadata' => $props['metadata'],
                                        'oi_media_type' => $props['media_type'],
@@ -2884,7 +2978,8 @@ class LocalFileRestoreBatch {
                                        'oi_minor_mime' => $props['minor_mime'],
                                        'oi_deleted' => $this->unsuppress ? 0 : $row->fa_deleted,
                                        'oi_sha1' => $sha1
-                               ] + $commentStore->insert( $dbw, 'oi_description', $comment );
+                               ] + $commentStore->insert( $dbw, 'oi_description', $comment )
+                               + $actorMigration->getInsertValues( $dbw, 'oi_user', $user );
                        }
 
                        $deleteIds[] = $row->fa_id;
index d08d0ae..65f0fb1 100644 (file)
@@ -110,7 +110,19 @@ class OldLocalFile extends LocalFile {
         * @return array
         */
        static function selectFields() {
+               global $wgActorTableSchemaMigrationStage;
+
                wfDeprecated( __METHOD__, '1.31' );
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH ) {
+                       // If code is using this instead of self::getQueryInfo(), there's a
+                       // decent chance it's going to try to directly access
+                       // $row->oi_user or $row->oi_user_text and we can't give it
+                       // useful values here once those aren't being written anymore.
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage > MIGRATION_WRITE_BOTH'
+                       );
+               }
+
                return [
                        'oi_name',
                        'oi_archive_name',
@@ -124,6 +136,7 @@ class OldLocalFile extends LocalFile {
                        'oi_minor_mime',
                        'oi_user',
                        'oi_user_text',
+                       'oi_actor' => $wgActorTableSchemaMigrationStage > MIGRATION_OLD ? 'oi_actor' : null,
                        'oi_timestamp',
                        'oi_deleted',
                        'oi_sha1',
@@ -143,8 +156,9 @@ class OldLocalFile extends LocalFile {
         */
        public static function getQueryInfo( array $options = [] ) {
                $commentQuery = CommentStore::getStore()->getJoin( 'oi_description' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'oi_user' );
                $ret = [
-                       'tables' => [ 'oldimage' ] + $commentQuery['tables'],
+                       'tables' => [ 'oldimage' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                'oi_name',
                                'oi_archive_name',
@@ -155,13 +169,11 @@ class OldLocalFile extends LocalFile {
                                'oi_media_type',
                                'oi_major_mime',
                                'oi_minor_mime',
-                               'oi_user',
-                               'oi_user_text',
                                'oi_timestamp',
                                'oi_deleted',
                                'oi_sha1',
-                       ] + $commentQuery['fields'],
-                       'joins' => $commentQuery['joins'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
+                       'joins' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
 
                if ( in_array( 'omit-nonlazy', $options, true ) ) {
@@ -435,6 +447,7 @@ class OldLocalFile extends LocalFile {
                }
 
                $commentFields = CommentStore::getStore()->insert( $dbw, 'oi_description', $comment );
+               $actorFields = ActorMigration::newMigration()->getInsertValues( $dbw, 'oi_user', $user );
                $dbw->insert( 'oldimage',
                        [
                                'oi_name' => $this->getName(),
@@ -444,14 +457,12 @@ class OldLocalFile extends LocalFile {
                                'oi_height' => intval( $props['height'] ),
                                'oi_bits' => $props['bits'],
                                'oi_timestamp' => $dbw->timestamp( $timestamp ),
-                               'oi_user' => $user->getId(),
-                               'oi_user_text' => $user->getName(),
                                'oi_metadata' => $props['metadata'],
                                'oi_media_type' => $props['media_type'],
                                'oi_major_mime' => $props['major_mime'],
                                'oi_minor_mime' => $props['minor_mime'],
                                'oi_sha1' => $props['sha1'],
-                       ] + $commentFields, __METHOD__
+                       ] + $commentFields + $actorFields, __METHOD__
                );
 
                return true;
index 4325a1a..55a7b2d 100644 (file)
@@ -609,14 +609,7 @@ class WikiRevision implements ImportableUploadRevision, ImportableOldRevision {
        public function importLogItem() {
                $dbw = wfGetDB( DB_MASTER );
 
-               $user = $this->getUserObj() ?: User::newFromName( $this->getUser() );
-               if ( $user ) {
-                       $userId = intval( $user->getId() );
-                       $userText = $user->getName();
-               } else {
-                       $userId = 0;
-                       $userText = $this->getUser();
-               }
+               $user = $this->getUserObj() ?: User::newFromName( $this->getUser(), false );
 
                # @todo FIXME: This will not record autoblocks
                if ( !$this->getTitle() ) {
@@ -632,7 +625,6 @@ class WikiRevision implements ImportableUploadRevision, ImportableOldRevision {
                                'log_timestamp' => $dbw->timestamp( $this->timestamp ),
                                'log_namespace' => $this->getTitle()->getNamespace(),
                                'log_title' => $this->getTitle()->getDBkey(),
-                               # 'log_user_text' => $this->user_text,
                                'log_params' => $this->params ],
                        __METHOD__
                );
@@ -647,12 +639,11 @@ class WikiRevision implements ImportableUploadRevision, ImportableOldRevision {
                        'log_type' => $this->type,
                        'log_action' => $this->action,
                        'log_timestamp' => $dbw->timestamp( $this->timestamp ),
-                       'log_user' => $userId,
-                       'log_user_text' => $userText,
                        'log_namespace' => $this->getTitle()->getNamespace(),
                        'log_title' => $this->getTitle()->getDBkey(),
                        'log_params' => $this->params
-               ] + CommentStore::getStore()->insert( $dbw, 'log_comment', $this->getComment() );
+               ] + CommentStore::getStore()->insert( $dbw, 'log_comment', $this->getComment() )
+                       + ActorMigration::newMigration()->getInsertValues( $dbw, 'log_user', $user );
                $dbw->insert( 'logging', $data, __METHOD__ );
 
                return true;
index 2083500..500bc5a 100644 (file)
@@ -1228,7 +1228,27 @@ abstract class DatabaseUpdater {
                        );
                        $task = $this->maintenance->runChild( MigrateComments::class, 'migrateComments.php' );
                        $task->execute();
-                       $this->output( "done.\n" );
+                       $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
+               }
+       }
+
+       /**
+        * Migrate actors to the new 'actor' table
+        * @since 1.31
+        */
+       protected function migrateActors() {
+               global $wgActorTableSchemaMigrationStage;
+               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_NEW &&
+                       !$this->updateRowExists( 'MigrateActors' )
+               ) {
+                       $this->output(
+                               "Migrating actors to the 'actor' table, printing progress markers. For large\n" .
+                               "databases, you may want to hit Ctrl-C and do this manually with\n" .
+                               "maintenance/migrateActors.php.\n"
+                       );
+                       $task = $this->maintenance->runChild( 'MigrateActors', 'migrateActors.php' );
+                       $ok = $task->execute();
+                       $this->output( $ok ? "done.\n" : "errors were encountered.\n" );
                }
        }
 
index 1fd1d9b..38a9ede 100644 (file)
@@ -116,6 +116,8 @@ class MssqlUpdater extends DatabaseUpdater {
                        [ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
                        [ 'addTable', 'content_models', 'patch-content_models.sql' ],
                        [ 'migrateArchiveText' ],
+                       [ 'addTable', 'actor', 'patch-actor-table.sql' ],
+                       [ 'migrateActors' ],
                ];
        }
 
index bce5405..350962f 100644 (file)
@@ -336,6 +336,8 @@ class MysqlUpdater extends DatabaseUpdater {
                        [ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
                        [ 'addTable', 'content_models', 'patch-content_models.sql' ],
                        [ 'migrateArchiveText' ],
+                       [ 'addTable', 'actor', 'patch-actor-table.sql' ],
+                       [ 'migrateActors' ],
                ];
        }
 
index 3ee51ea..60ac23c 100644 (file)
@@ -137,6 +137,8 @@ class OracleUpdater extends DatabaseUpdater {
                        [ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
                        [ 'addTable', 'content_models', 'patch-content_models.sql' ],
                        [ 'migrateArchiveText' ],
+                       [ 'addTable', 'actor', 'patch-actor-table.sql' ],
+                       [ 'migrateActors' ],
 
                        // KEEP THIS AT THE BOTTOM!!
                        [ 'doRebuildDuplicateFunction' ],
index 8d12404..2bfadf4 100644 (file)
@@ -491,6 +491,39 @@ class PostgresUpdater extends DatabaseUpdater {
                        [ 'addTable', 'content_models', 'patch-content_models-table.sql' ],
                        [ 'addTable', 'slot_roles', 'patch-slot_roles-table.sql' ],
                        [ 'migrateArchiveText' ],
+                       [ 'addTable', 'actor', 'patch-actor-table.sql' ],
+                       [ 'setDefault', 'revision', 'rev_user', 0 ],
+                       [ 'setDefault', 'revision', 'rev_user_text', '' ],
+                       [ 'setDefault', 'archive', 'ar_user', 0 ],
+                       [ 'changeNullableField', 'archive', 'ar_user', 'NOT NULL', true ],
+                       [ 'setDefault', 'archive', 'ar_user_text', '' ],
+                       [ 'addPgField', 'archive', 'ar_actor', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'addPgIndex', 'archive', 'archive_actor', '( ar_actor )' ],
+                       [ 'setDefault', 'ipblocks', 'ipb_by', 0 ],
+                       [ 'addPgField', 'ipblocks', 'ipb_by_actor', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'image', 'img_user', 0 ],
+                       [ 'changeNullableField', 'image', 'img_user', 'NOT NULL', true ],
+                       [ 'setDefault', 'image', 'img_user_text', '' ],
+                       [ 'addPgField', 'image', 'img_actor', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'oldimage', 'oi_user', 0 ],
+                       [ 'changeNullableField', 'oldimage', 'oi_user', 'NOT NULL', true ],
+                       [ 'setDefault', 'oldimage', 'oi_user_text', '' ],
+                       [ 'addPgField', 'oldimage', 'oi_actor', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'filearchive', 'fa_user', 0 ],
+                       [ 'changeNullableField', 'filearchive', 'fa_user', 'NOT NULL', true ],
+                       [ 'setDefault', 'filearchive', 'fa_user_text', '' ],
+                       [ 'addPgField', 'filearchive', 'fa_actor', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'recentchanges', 'rc_user', 0 ],
+                       [ 'changeNullableField', 'recentchanges', 'rc_user', 'NOT NULL', true ],
+                       [ 'setDefault', 'recentchanges', 'rc_user_text', '' ],
+                       [ 'addPgField', 'recentchanges', 'rc_actor', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'setDefault', 'logging', 'log_user', 0 ],
+                       [ 'changeNullableField', 'logging', 'log_user', 'NOT NULL', true ],
+                       [ 'addPgField', 'logging', 'log_actor', 'INTEGER NOT NULL DEFAULT 0' ],
+                       [ 'addPgIndex', 'logging', 'logging_actor_time_backwards', '( log_timestamp, log_actor )' ],
+                       [ 'addPgIndex', 'logging', 'logging_actor_type_time', '( log_actor, log_type, log_timestamp )' ],
+                       [ 'addPgIndex', 'logging', 'logging_actor_time', '( log_actor, log_timestamp )' ],
+                       [ 'migrateActors' ],
                ];
        }
 
index afb8b22..3a755b6 100644 (file)
@@ -200,6 +200,8 @@ class SqliteUpdater extends DatabaseUpdater {
                        [ 'addTable', 'slots', 'patch-slots.sql' ],
                        [ 'addTable', 'slot_roles', 'patch-slot_roles.sql' ],
                        [ 'migrateArchiveText' ],
+                       [ 'addTable', 'actor', 'patch-actor-table.sql' ],
+                       [ 'migrateActors' ],
                ];
        }
 
index 05df002..c07bdc6 100644 (file)
@@ -80,7 +80,7 @@
        "config-env-bad": "El entorno ha sido comprobado.\nNo puedes instalar MediaWiki.",
        "config-env-php": "PHP $1 está instalado.",
        "config-env-hhvm": "HHVM $1 está instalado.",
-       "config-unicode-using-intl": "Usando la [https://pecl.php.net/intl extensión intl PECL] para la normalización Unicode.",
+       "config-unicode-using-intl": "Se utiliza la [https://pecl.php.net/intl extensión «intl» de PECL] para la normalización Unicode.",
        "config-unicode-pure-php-warning": "<strong>Advertencia:</strong> la [https://pecl.php.net/intl extensión intl] no está disponible para efectuar la normalización Unicode. Se utilizará la implementación más lenta en PHP puro.\nSi tu web tiene mucho tráfico, te recomendamos leer acerca de la [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations normalización Unicode].",
        "config-unicode-update-warning": "<strong>Atención:</strong> la versión instalada del contenedor de normalización de Unicode utiliza una versión anticuada de la biblioteca del [http://site.icu-project.org/ proyecto ICU].\nDeberías [https://www.mediawiki.org/wiki/Special:MyLanguage/Unicode_normalization_considerations modernizarla] si te interesa utilizar Unicode.",
        "config-no-db": "No se encontró un controlador adecuado para la base de datos. Necesitas instalar un controlador de base de datos para PHP.\n{{PLURAL:$2|Se admite el siguiente gestor de bases de datos|Se admiten los siguientes gestores de bases de datos}}: $1.\n\nSi compilaste PHP por tu cuenta, debes reconfigurarlo activando un cliente de base de datos, por ejemplo, mediante <code>./configure --with-mysqli</code>.\nSi instalaste PHP desde un paquete de Debian o Ubuntu, también debes instalar, por ejemplo, el paquete <code>php5-mysql</code>.",
index d97e4f9..77daca7 100644 (file)
@@ -158,11 +158,15 @@ class RecentChangesUpdateJob extends Job {
                                $eTimestamp = min( $sTimestamp + $window, $nowUnix );
 
                                // Get all the users active since the last update
+                               $actorQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
                                $res = $dbw->select(
-                                       [ 'recentchanges' ],
-                                       [ 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ],
+                                       [ 'recentchanges' ] + $actorQuery['tables'],
                                        [
-                                               'rc_user > 0', // actual accounts
+                                               'rc_user_text' => $actorQuery['fields']['rc_user_text'],
+                                               'lastedittime' => 'MAX(rc_timestamp)'
+                                       ],
+                                       [
+                                               $actorQuery['fields']['rc_user'] . ' > 0', // actual accounts
                                                'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata
                                                'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ),
                                                'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ),
@@ -172,7 +176,8 @@ class RecentChangesUpdateJob extends Job {
                                        [
                                                'GROUP BY' => [ 'rc_user_text' ],
                                                'ORDER BY' => 'NULL' // avoid filesort
-                                       ]
+                                       ],
+                                       $actorQuery['joins']
                                );
                                $names = [];
                                foreach ( $res as $row ) {
index f2c7ed2..3d1c8b8 100644 (file)
@@ -535,8 +535,8 @@ class CSSMin {
        public static function minify( $css ) {
                return trim(
                        str_replace(
-                               [ '; ', ': ', ' {', '{ ', ', ', '} ', ';}' ],
-                               [ ';', ':', '{', '{', ',', '}', '}' ],
+                               [ '; ', ': ', ' {', '{ ', ', ', '} ', ';}', '( ', ' )', '[ ', ' ]' ],
+                               [ ';', ':', '{', '{', ',', '}', '}', '(', ')', '[', ']' ],
                                preg_replace( [ '/\s+/', '/\/\*.*?\*\//s' ], [ ' ', '' ], $css )
                        )
                );
index bcd95c1..17f596d 100644 (file)
@@ -517,18 +517,18 @@ class WANObjectCache implements IExpiringStore, LoggerAwareInterface {
                        // Case B: any long-running transaction; ignore this set()
                        } elseif ( $age > self::MAX_READ_LAG ) {
                                $this->logger->info( 'Rejected set() for {cachekey} due to snapshot lag.',
-                                       [ 'cachekey' => $key ] );
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
 
                                return true; // no-op the write for being unsafe
                        // Case C: high replication lag; lower TTL instead of ignoring all set()s
                        } elseif ( $lag === false || $lag > self::MAX_READ_LAG ) {
                                $ttl = $ttl ? min( $ttl, self::TTL_LAGGED ) : self::TTL_LAGGED;
                                $this->logger->warning( 'Lowered set() TTL for {cachekey} due to replication lag.',
-                                       [ 'cachekey' => $key ] );
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
                        // Case D: medium length request with medium replication lag; ignore this set()
                        } else {
                                $this->logger->info( 'Rejected set() for {cachekey} due to high read lag.',
-                                       [ 'cachekey' => $key ] );
+                                       [ 'cachekey' => $key, 'lag' => $lag, 'age' => $age ] );
 
                                return true; // no-op the write for being unsafe
                        }
index 88e276f..e115286 100644 (file)
@@ -75,7 +75,7 @@ class ChronologyProtector implements LoggerAwareInterface {
        public function __construct( BagOStuff $store, array $client, $posIndex = null ) {
                $this->store = $store;
                $this->clientId = md5( $client['ip'] . "\n" . $client['agent'] );
-               $this->key = $store->makeGlobalKey( __CLASS__, $this->clientId, 'v1' );
+               $this->key = $store->makeGlobalKey( __CLASS__, $this->clientId, 'v2' );
                $this->waitForPosIndex = $posIndex;
                $this->logger = new NullLogger();
        }
index d59bee3..2922bce 100644 (file)
@@ -962,11 +962,11 @@ interface IDatabase {
         * Example usage:
         * @code
         *     $sql = $db->makeList( [
-        *         'rev_user' => $id,
+        *         'rev_page' => $id,
         *         $db->makeList( [ 'rev_minor' => 1, 'rev_len' < 500 ], $db::LIST_OR ] )
         *     ], $db::LIST_AND );
         * @endcode
-        * This would set $sql to "rev_user = '$id' AND (rev_minor = '1' OR rev_len < '500')"
+        * This would set $sql to "rev_page = '$id' AND (rev_minor = '1' OR rev_len < '500')"
         *
         * @param array $a Containing the data
         * @param int $mode IDatabase class constant:
index 2f79ea9..28d2a1b 100644 (file)
@@ -2,12 +2,14 @@
 
 namespace Wikimedia\Rdbms;
 
+use Serializable;
+
 /**
  * An object representing a master or replica DB position in a replicated setup.
  *
  * The implementation details of this opaque type are up to the database subclass.
  */
-interface DBMasterPos {
+interface DBMasterPos extends Serializable {
        /**
         * @return float UNIX timestamp
         * @since 1.25
index 2ee9068..cdcb79c 100644 (file)
@@ -3,6 +3,7 @@
 namespace Wikimedia\Rdbms;
 
 use InvalidArgumentException;
+use UnexpectedValueException;
 
 /**
  * DBMasterPos class for MySQL/MariaDB
@@ -27,6 +28,14 @@ class MySQLMasterPos implements DBMasterPos {
         * @param float $asOfTime UNIX timestamp
         */
        public function __construct( $position, $asOfTime ) {
+               $this->init( $position, $asOfTime );
+       }
+
+       /**
+        * @param string $position
+        * @param float $asOfTime
+        */
+       protected function init( $position, $asOfTime ) {
                $m = [];
                if ( preg_match( '!^(.+)\.(\d+)/(\d+)$!', $position, $m ) ) {
                        $this->binlog = $m[1]; // ideally something like host name
@@ -34,7 +43,7 @@ class MySQLMasterPos implements DBMasterPos {
                } else {
                        $gtids = array_filter( array_map( 'trim', explode( ',', $position ) ) );
                        foreach ( $gtids as $gtid ) {
-                               if ( !$this->parseGTID( $gtid ) ) {
+                               if ( !self::parseGTID( $gtid ) ) {
                                        throw new InvalidArgumentException( "Invalid GTID '$gtid'." );
                                }
                                $this->gtids[] = $gtid;
@@ -192,4 +201,17 @@ class MySQLMasterPos implements DBMasterPos {
                        ? [ 'binlog' => $this->binlog, 'pos' => $this->pos ]
                        : false;
        }
+
+       public function serialize() {
+               return serialize( [ 'position' => $this->__toString(), 'asOfTime' => $this->asOfTime ] );
+       }
+
+       public function unserialize( $serialized ) {
+               $data = unserialize( $serialized );
+               if ( !is_array( $data ) ) {
+                       throw new UnexpectedValueException( __METHOD__ . ": cannot unserialize position" );
+               }
+
+               $this->init( $data['position'], $data['asOfTime'] );
+       }
 }
index 35502c7..395110b 100644 (file)
@@ -171,20 +171,22 @@ class DatabaseLogEntry extends LogEntryBase {
         */
        public static function getSelectQueryData() {
                $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
 
-               $tables = [ 'logging', 'user' ] + $commentQuery['tables'];
+               $tables = array_merge(
+                       [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ]
+               );
                $fields = [
                        'log_id', 'log_type', 'log_action', 'log_timestamp',
-                       'log_user', 'log_user_text',
                        'log_namespace', 'log_title', // unused log_page
                        'log_params', 'log_deleted',
                        'user_id', 'user_name', 'user_editcount',
-               ] + $commentQuery['fields'];
+               ] + $commentQuery['fields'] + $actorQuery['fields'];
 
                $joins = [
                        // IPs don't have an entry in user table
-                       'user' => [ 'LEFT JOIN', 'log_user=user_id' ],
-               ] + $commentQuery['joins'];
+                       'user' => [ 'LEFT JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ],
+               ] + $commentQuery['joins'] + $actorQuery['joins'];
 
                return [
                        'tables' => $tables,
@@ -293,11 +295,14 @@ class DatabaseLogEntry extends LogEntryBase {
 
        public function getPerformer() {
                if ( !$this->performer ) {
+                       $actorId = isset( $this->row->log_actor ) ? (int)$this->row->log_actor : 0;
                        $userId = (int)$this->row->log_user;
-                       if ( $userId !== 0 ) {
+                       if ( $userId !== 0 || $actorId !== 0 ) {
                                // logged-in users
                                if ( isset( $this->row->user_name ) ) {
                                        $this->performer = User::newFromRow( $this->row );
+                               } elseif ( $actorId !== 0 ) {
+                                       $this->performer = User::newFromActorId( $actorId );
                                } else {
                                        $this->performer = User::newFromId( $userId );
                                }
@@ -356,8 +361,11 @@ class RCDatabaseLogEntry extends DatabaseLogEntry {
 
        public function getPerformer() {
                if ( !$this->performer ) {
+                       $actorId = isset( $this->row->rc_actor ) ? (int)$this->row->rc_actor : 0;
                        $userId = (int)$this->row->rc_user;
-                       if ( $userId !== 0 ) {
+                       if ( $actorId !== 0 ) {
+                               $this->performer = User::newFromActorId( $actorId );
+                       } elseif ( $userId !== 0 ) {
                                $this->performer = User::newFromId( $userId );
                        } else {
                                $userText = $this->row->rc_user_text;
@@ -593,6 +601,8 @@ class ManualLogEntry extends LogEntryBase {
         * @throws MWException
         */
        public function insert( IDatabase $dbw = null ) {
+               global $wgActorTableSchemaMigrationStage;
+
                $dbw = $dbw ?: wfGetDB( DB_MASTER );
 
                if ( $this->timestamp === null ) {
@@ -605,6 +615,31 @@ class ManualLogEntry extends LogEntryBase {
                $params = $this->getParameters();
                $relations = $this->relations;
 
+               // Ensure actor relations are set
+               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH &&
+                       empty( $relations['target_author_actor'] )
+               ) {
+                       $actorIds = [];
+                       if ( !empty( $relations['target_author_id'] ) ) {
+                               foreach ( $relations['target_author_id'] as $id ) {
+                                       $actorIds[] = User::newFromId( $id )->getActorId( $dbw );
+                               }
+                       }
+                       if ( !empty( $relations['target_author_ip'] ) ) {
+                               foreach ( $relations['target_author_ip'] as $ip ) {
+                                       $actorIds[] = User::newFromName( $ip, false )->getActorId( $dbw );
+                               }
+                       }
+                       if ( $actorIds ) {
+                               $relations['target_author_actor'] = $actorIds;
+                               $params['authorActors'] = $actorIds;
+                       }
+               }
+               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_NEW ) {
+                       unset( $relations['target_author_id'], $relations['target_author_ip'] );
+                       unset( $params['authorIds'], $params['authorIPs'] );
+               }
+
                // Additional fields for which there's no space in the database table schema
                $revId = $this->getAssociatedRevId();
                if ( $revId ) {
@@ -616,8 +651,6 @@ class ManualLogEntry extends LogEntryBase {
                        'log_type' => $this->getType(),
                        'log_action' => $this->getSubtype(),
                        'log_timestamp' => $dbw->timestamp( $this->getTimestamp() ),
-                       'log_user' => $this->getPerformer()->getId(),
-                       'log_user_text' => $this->getPerformer()->getName(),
                        'log_namespace' => $this->getTarget()->getNamespace(),
                        'log_title' => $this->getTarget()->getDBkey(),
                        'log_page' => $this->getTarget()->getArticleID(),
@@ -627,6 +660,8 @@ class ManualLogEntry extends LogEntryBase {
                        $data['log_deleted'] = $this->deleted;
                }
                $data += CommentStore::getStore()->insert( $dbw, 'log_comment', $comment );
+               $data += ActorMigration::newMigration()
+                       ->getInsertValues( $dbw, 'log_user', $this->getPerformer() );
 
                $dbw->insert( 'logging', $data, __METHOD__ );
                $this->id = $dbw->insertId();
index c84352e..28c1a87 100644 (file)
@@ -97,14 +97,13 @@ class LogPage {
                        'log_type' => $this->type,
                        'log_action' => $this->action,
                        'log_timestamp' => $dbw->timestamp( $now ),
-                       'log_user' => $this->doer->getId(),
-                       'log_user_text' => $this->doer->getName(),
                        'log_namespace' => $this->target->getNamespace(),
                        'log_title' => $this->target->getDBkey(),
                        'log_page' => $this->target->getArticleID(),
                        'log_params' => $this->params
                ];
                $data += CommentStore::getStore()->insert( $dbw, 'log_comment', $this->comment );
+               $data += ActorMigration::newMigration()->getInsertValues( $dbw, 'log_user', $this->doer );
                $dbw->insert( 'logging', $data, __METHOD__ );
                $newId = $dbw->insertId();
 
index 5404f35..dc9af5a 100644 (file)
@@ -176,13 +176,14 @@ class LogPager extends ReverseChronologicalPager {
                // Normalize username first so that non-existent users used
                // in maintenance scripts work
                $name = $usertitle->getText();
-               /* Fetch userid at first, if known, provides awesome query plan afterwards */
-               $userid = User::idFromName( $name );
-               if ( !$userid ) {
-                       $this->mConds['log_user_text'] = IP::sanitizeIP( $name );
-               } else {
-                       $this->mConds['log_user'] = $userid;
-               }
+
+               // Assume no joins required for log_user
+               // Don't query by user ID here, it might be able to use the
+               // log_user_text_time or log_user_text_type_time index.
+               $this->mConds[] = ActorMigration::newMigration()->getWhere(
+                       wfGetDB( DB_REPLICA ), 'log_user', User::newFromName( $name, false ), false
+               )['conds'];
+
                $this->enforcePerformerRestrictions();
 
                $this->performer = $name;
index 0bd01cd..3c778f3 100644 (file)
@@ -158,6 +158,8 @@ class JpegMetadataExtractor {
                                if ( $size['int'] < 2 ) {
                                        throw new MWException( "invalid marker size in jpeg" );
                                }
+                               // Note it's possible to seek beyond end of file if truncated.
+                               // fseek doesn't report a failure in this case.
                                fseek( $fh, $size['int'] - 2, SEEK_CUR );
                        }
                }
index 544d23d..d266e1d 100644 (file)
@@ -1036,11 +1036,15 @@ class WikiPage implements Page, IDBAccessObject {
 
                $dbr = wfGetDB( DB_REPLICA );
 
-               $tables = [ 'revision', 'user' ];
+               $actorMigration = ActorMigration::newMigration();
+               $actorQuery = $actorMigration->getJoin( 'rev_user' );
+
+               $tables = array_merge( [ 'revision' ], $actorQuery['tables'], [ 'user' ] );
 
                $fields = [
-                       'user_id' => 'rev_user',
-                       'user_name' => 'rev_user_text',
+                       'user_id' => $actorQuery['fields']['rev_user'],
+                       'user_name' => $actorQuery['fields']['rev_user_text'],
+                       'actor_id' => $actorQuery['fields']['rev_actor'],
                        'user_real_name' => 'MIN(user_real_name)',
                        'timestamp' => 'MAX(rev_timestamp)',
                ];
@@ -1049,22 +1053,20 @@ class WikiPage implements Page, IDBAccessObject {
 
                // The user who made the top revision gets credited as "this page was last edited by
                // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
-               $user = $this->getUser();
-               if ( $user ) {
-                       $conds[] = "rev_user != $user";
-               } else {
-                       $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
-               }
+               $user = $this->getUser()
+                       ? User::newFromId( $this->getUser() )
+                       : User::newFromName( $this->getUserText(), false );
+               $conds[] = 'NOT(' . $actorMigration->getWhere( $dbr, 'rev_user', $user )['conds'] . ')';
 
                // Username hidden?
                $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0";
 
                $jconds = [
-                       'user' => [ 'LEFT JOIN', 'rev_user = user_id' ],
-               ];
+                       'user' => [ 'LEFT JOIN', $actorQuery['fields']['rev_user'] . ' = user_id' ],
+               ] + $actorQuery['joins'];
 
                $options = [
-                       'GROUP BY' => [ 'rev_user', 'rev_user_text' ],
+                       'GROUP BY' => [ $fields['user_id'], $fields['user_name'] ],
                        'ORDER BY' => 'timestamp DESC',
                ];
 
@@ -2783,7 +2785,8 @@ class WikiPage implements Page, IDBAccessObject {
                $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
                $tags = [], $logsubtype = 'delete'
        ) {
-               global $wgUser, $wgContentHandlerUseDB, $wgCommentTableSchemaMigrationStage;
+               global $wgUser, $wgContentHandlerUseDB, $wgCommentTableSchemaMigrationStage,
+                       $wgActorTableSchemaMigrationStage;
 
                wfDebug( __METHOD__ . "\n" );
 
@@ -2848,6 +2851,7 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                $commentStore = CommentStore::getStore();
+               $actorMigration = ActorMigration::newMigration();
 
                $revQuery = Revision::getQueryInfo();
                $bitfield = false;
@@ -2884,11 +2888,10 @@ class WikiPage implements Page, IDBAccessObject {
 
                foreach ( $res as $row ) {
                        $comment = $commentStore->getComment( 'rev_comment', $row );
+                       $user = User::newFromAnyId( $row->rev_user, $row->rev_user_text, $row->rev_actor );
                        $rowInsert = [
                                'ar_namespace'  => $namespace,
                                'ar_title'      => $dbKey,
-                               'ar_user'       => $row->rev_user,
-                               'ar_user_text'  => $row->rev_user_text,
                                'ar_timestamp'  => $row->rev_timestamp,
                                'ar_minor_edit' => $row->rev_minor_edit,
                                'ar_rev_id'     => $row->rev_id,
@@ -2900,7 +2903,8 @@ class WikiPage implements Page, IDBAccessObject {
                                'ar_page_id'    => $id,
                                'ar_deleted'    => $suppress ? $bitfield : $row->rev_deleted,
                                'ar_sha1'       => $row->rev_sha1,
-                       ] + $commentStore->insert( $dbw, 'ar_comment', $comment );
+                       ] + $commentStore->insert( $dbw, 'ar_comment', $comment )
+                               + $actorMigration->getInsertValues( $dbw, 'ar_user', $user );
                        if ( $wgContentHandlerUseDB ) {
                                $rowInsert['ar_content_model'] = $row->rev_content_model;
                                $rowInsert['ar_content_format'] = $row->rev_content_format;
@@ -2930,6 +2934,9 @@ class WikiPage implements Page, IDBAccessObject {
                if ( $wgCommentTableSchemaMigrationStage > MIGRATION_OLD ) {
                        $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
                }
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
+               }
 
                // Also delete records from ip_changes as applicable.
                if ( count( $ipRevIds ) > 0 ) {
@@ -3161,16 +3168,31 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Get the last edit not by this person...
                // Note: these may not be public values
-               $user = intval( $current->getUser( Revision::RAW ) );
-               $user_text = $dbw->addQuotes( $current->getUserText( Revision::RAW ) );
-               $s = $dbw->selectRow( 'revision',
+               $userId = intval( $current->getUser( Revision::RAW ) );
+               $userName = $current->getUserText( Revision::RAW );
+               if ( $userId ) {
+                       $user = User::newFromId( $userId );
+                       $user->setName( $userName );
+               } else {
+                       $user = User::newFromName( $current->getUserText( Revision::RAW ), false );
+               }
+
+               $actorWhere = ActorMigration::newMigration()->getWhere( $dbw, 'rev_user', $user );
+
+               $s = $dbw->selectRow(
+                       [ 'revision' ] + $actorWhere['tables'],
                        [ 'rev_id', 'rev_timestamp', 'rev_deleted' ],
-                       [ 'rev_page' => $current->getPage(),
-                               "rev_user != {$user} OR rev_user_text != {$user_text}"
-                       ], __METHOD__,
-                       [ 'USE INDEX' => 'page_timestamp',
-                               'ORDER BY' => 'rev_timestamp DESC' ]
-                       );
+                       [
+                               'rev_page' => $current->getPage(),
+                               'NOT(' . $actorWhere['conds'] . ')',
+                       ],
+                       __METHOD__,
+                       [
+                               'USE INDEX' => [ 'revision' => 'page_timestamp' ],
+                               'ORDER BY' => 'rev_timestamp DESC'
+                       ],
+                       $actorWhere['joins']
+               );
                if ( $s === false ) {
                        // No one else ever edited this page
                        return [ [ 'cantrollback' ] ];
@@ -3249,11 +3271,12 @@ class WikiPage implements Page, IDBAccessObject {
                }
 
                if ( count( $set ) ) {
+                       $actorWhere = ActorMigration::newMigration()->getWhere( $dbw, 'rc_user', $user, false );
                        $dbw->update( 'recentchanges', $set,
                                [ /* WHERE */
                                        'rc_cur_id' => $current->getPage(),
-                                       'rc_user_text' => $current->getUserText(),
                                        'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ),
+                                       $actorWhere['conds'], // No tables/joins are needed for rc_user
                                ],
                                __METHOD__
                        );
index 8b9feeb..e5fe928 100644 (file)
@@ -66,6 +66,7 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                }
 
                $illegalFileChars = $conf->get( 'IllegalFileChars' );
+               $oldCommentSchema = $conf->get( 'CommentTableSchemaMigrationStage' ) === MIGRATION_OLD;
 
                // Build list of variables
                $vars = [
@@ -113,6 +114,8 @@ class ResourceLoaderStartUpModule extends ResourceLoaderModule {
                        'wgResourceLoaderStorageEnabled' => $conf->get( 'ResourceLoaderStorageEnabled' ),
                        'wgForeignUploadTargets' => $conf->get( 'ForeignUploadTargets' ),
                        'wgEnableUploads' => $conf->get( 'EnableUploads' ),
+                       'wgCommentByteLimit' => $oldCommentSchema ? 255 : null,
+                       'wgCommentCodePointLimit' => $oldCommentSchema ? null : CommentStore::COMMENT_CHARACTER_LIMIT,
                ];
 
                Hooks::run( 'ResourceLoaderGetConfigVars', [ &$vars ] );
index ab74dbd..679acc6 100644 (file)
@@ -45,6 +45,10 @@ class RevDelArchiveItem extends RevDelRevisionItem {
                return 'ar_user_text';
        }
 
+       public function getAuthorActorField() {
+               return 'ar_actor';
+       }
+
        public function getId() {
                # Convert DB timestamp to MW timestamp
                return $this->revision->getTimestamp();
index b098422..d36fac9 100644 (file)
@@ -50,6 +50,10 @@ class RevDelArchivedFileItem extends RevDelFileItem {
                return 'fa_user_text';
        }
 
+       public function getAuthorActorField() {
+               return 'fa_actor';
+       }
+
        public function getId() {
                return $this->row->fa_id;
        }
index 9beafc9..0ca84d7 100644 (file)
@@ -49,6 +49,10 @@ class RevDelFileItem extends RevDelItem {
                return 'oi_user_text';
        }
 
+       public function getAuthorActorField() {
+               return 'oi_actor';
+       }
+
        public function getId() {
                $parts = explode( '!', $this->row->oi_archive_name );
 
index 011c7b0..89025bc 100644 (file)
@@ -106,6 +106,8 @@ abstract class RevDelList extends RevisionListBase {
         * @since 1.23 Added 'perItemStatus' param
         */
        public function setVisibility( array $params ) {
+               global $wgActorTableSchemaMigrationStage;
+
                $status = Status::newGood();
 
                $bitPars = $params['value'];
@@ -134,7 +136,7 @@ abstract class RevDelList extends RevisionListBase {
                $missing = array_flip( $this->ids );
                $this->clearFileOps();
                $idsForLog = [];
-               $authorIds = $authorIPs = [];
+               $authorIds = $authorIPs = $authorActors = [];
 
                if ( $perItemStatus ) {
                        $status->itemStatuses = [];
@@ -216,10 +218,15 @@ abstract class RevDelList extends RevisionListBase {
                                $virtualOldBits |= $removedBits;
 
                                $status->successCount++;
-                               if ( $item->getAuthorId() > 0 ) {
-                                       $authorIds[] = $item->getAuthorId();
-                               } elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
-                                       $authorIPs[] = $item->getAuthorName();
+                               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                                       if ( $item->getAuthorId() > 0 ) {
+                                               $authorIds[] = $item->getAuthorId();
+                                       } elseif ( IP::isIPAddress( $item->getAuthorName() ) ) {
+                                               $authorIPs[] = $item->getAuthorName();
+                                       }
+                               }
+                               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                                       $authorActors[] = $item->getAuthorActor();
                                }
 
                                // Save the old and new bits in $visibilityChangeMap for
@@ -263,6 +270,14 @@ abstract class RevDelList extends RevisionListBase {
                }
 
                // Log it
+               $authorFields = [];
+               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
+                       $authorFields['authorIds'] = $authorIds;
+                       $authorFields['authorIPs'] = $authorIPs;
+               }
+               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                       $authorFields['authorActors'] = $authorActors;
+               }
                $this->updateLog(
                        $logType,
                        [
@@ -272,10 +287,8 @@ abstract class RevDelList extends RevisionListBase {
                                'oldBits' => $virtualOldBits,
                                'comment' => $comment,
                                'ids' => $idsForLog,
-                               'authorIds' => $authorIds,
-                               'authorIPs' => $authorIPs,
                                'tags' => isset( $params['tags'] ) ? $params['tags'] : [],
-                       ]
+                       ] + $authorFields
                );
 
                // Clear caches after commit
@@ -330,8 +343,9 @@ abstract class RevDelList extends RevisionListBase {
         *     title:           The target title
         *     ids:             The ID list
         *     comment:         The log comment
-        *     authorsIds:      The array of the user IDs of the offenders
-        *     authorsIPs:      The array of the IP/anon user offenders
+        *     authorIds:       The array of the user IDs of the offenders
+        *     authorIPs:       The array of the IP/anon user offenders
+        *     authorActors:    The array of the actor IDs of the offenders
         *     tags:            The array of change tags to apply to the log entry
         * @throws MWException
         */
@@ -350,11 +364,21 @@ abstract class RevDelList extends RevisionListBase {
                $logEntry->setParameters( $logParams );
                $logEntry->setPerformer( $this->getUser() );
                // Allow for easy searching of deletion log items for revision/log items
-               $logEntry->setRelations( [
+               $relations = [
                        $field => $params['ids'],
-                       'target_author_id' => $params['authorIds'],
-                       'target_author_ip' => $params['authorIPs'],
-               ] );
+               ];
+               if ( isset( $params['authorIds'] ) ) {
+                       $relations += [
+                               'target_author_id' => $params['authorIds'],
+                               'target_author_ip' => $params['authorIPs'],
+                       ];
+               }
+               if ( isset( $params['authorActors'] ) ) {
+                       $relations += [
+                               'target_author_actor' => $params['authorActors'],
+                       ];
+               }
+               $logEntry->setRelations( $relations );
                // Apply change tags to the log entry
                $logEntry->setTags( $params['tags'] );
                $logId = $logEntry->insert();
index b8b0c5c..198a28b 100644 (file)
@@ -39,6 +39,10 @@ class RevDelLogItem extends RevDelItem {
                return 'log_user_text';
        }
 
+       public function getAuthorActorField() {
+               return 'log_actor';
+       }
+
        public function canView() {
                return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED, $this->list->getUser() );
        }
index f4e4faf..b26fffd 100644 (file)
@@ -64,26 +64,25 @@ class RevDelLogList extends RevDelList {
                $ids = array_map( 'intval', $this->ids );
 
                $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
 
                return $db->select(
-                       [ 'logging' ] + $commentQuery['tables'],
+                       [ 'logging' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        [
                                'log_id',
                                'log_type',
                                'log_action',
                                'log_timestamp',
-                               'log_user',
-                               'log_user_text',
                                'log_namespace',
                                'log_title',
                                'log_page',
                                'log_params',
                                'log_deleted'
-                       ] + $commentQuery['fields'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
                        [ 'log_id' => $ids ],
                        __METHOD__,
                        [ 'ORDER BY' => 'log_id DESC' ],
-                       $commentQuery['joins']
+                       $commentQuery['joins'] + $actorQuery['joins']
                );
        }
 
index a9753b4..cb5ce48 100644 (file)
@@ -47,6 +47,10 @@ class RevDelRevisionItem extends RevDelItem {
                return 'rev_user_text';
        }
 
+       public function getAuthorActorField() {
+               return 'rev_actor';
+       }
+
        public function canView() {
                return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->list->getUser() );
        }
index 7812fb9..6291e8d 100644 (file)
@@ -42,6 +42,8 @@ class RevisionDeleteUser {
         * @return bool
         */
        private static function setUsernameBitfields( $name, $userId, $op, $dbw ) {
+               global $wgActorTableSchemaMigrationStage;
+
                if ( !$userId || ( $op !== '|' && $op !== '&' ) ) {
                        return false; // sanity check
                }
@@ -65,43 +67,120 @@ class RevisionDeleteUser {
                $userTitle = Title::makeTitleSafe( NS_USER, $name );
                $userDbKey = $userTitle->getDBkey();
 
-               # Hide name from live edits
-               $dbw->update(
-                       'revision',
-                       [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
-                       [ 'rev_user' => $userId ],
-                       __METHOD__ );
+               if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                       # Hide name from live edits
+                       $dbw->update(
+                               'revision',
+                               [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
+                               [ 'rev_user' => $userId ],
+                               __METHOD__ );
 
-               # Hide name from deleted edits
-               $dbw->update(
-                       'archive',
-                       [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
-                       [ 'ar_user_text' => $name ],
-                       __METHOD__
-               );
+                       # Hide name from deleted edits
+                       $dbw->update(
+                               'archive',
+                               [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
+                               [ 'ar_user_text' => $name ],
+                               __METHOD__
+                       );
 
-               # Hide name from logs
-               $dbw->update(
-                       'logging',
-                       [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
-                       [ 'log_user' => $userId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
-                       __METHOD__
-               );
+                       # Hide name from logs
+                       $dbw->update(
+                               'logging',
+                               [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
+                               [ 'log_user' => $userId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+                               __METHOD__
+                       );
+
+                       # Hide name from RC
+                       $dbw->update(
+                               'recentchanges',
+                               [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
+                               [ 'rc_user_text' => $name ],
+                               __METHOD__
+                       );
+
+                       # Hide name from live images
+                       $dbw->update(
+                               'oldimage',
+                               [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
+                               [ 'oi_user_text' => $name ],
+                               __METHOD__
+                       );
+
+                       # Hide name from deleted images
+                       $dbw->update(
+                               'filearchive',
+                               [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
+                               [ 'fa_user_text' => $name ],
+                               __METHOD__
+                       );
+               }
+
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       $actorId = $dbw->selectField( 'actor', 'actor_id', [ 'actor_name' => $name ], __METHOD__ );
+                       if ( $actorId ) {
+                               # Hide name from live edits
+                               $subquery = $dbw->selectSQLText(
+                                       'revision_actor_temp', 'revactor_rev', [ 'revactor_actor' => $actorId ], __METHOD__
+                               );
+                               $dbw->update(
+                                       'revision',
+                                       [ self::buildSetBitDeletedField( 'rev_deleted', $op, $delUser, $dbw ) ],
+                                       [ "rev_id IN ($subquery)" ],
+                                       __METHOD__ );
+
+                               # Hide name from deleted edits
+                               $dbw->update(
+                                       'archive',
+                                       [ self::buildSetBitDeletedField( 'ar_deleted', $op, $delUser, $dbw ) ],
+                                       [ 'ar_actor' => $actorId ],
+                                       __METHOD__
+                               );
+
+                               # Hide name from logs
+                               $dbw->update(
+                                       'logging',
+                                       [ self::buildSetBitDeletedField( 'log_deleted', $op, $delUser, $dbw ) ],
+                                       [ 'log_actor' => $actorId, 'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+                                       __METHOD__
+                               );
+
+                               # Hide name from RC
+                               $dbw->update(
+                                       'recentchanges',
+                                       [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
+                                       [ 'rc_actor' => $actorId ],
+                                       __METHOD__
+                               );
+
+                               # Hide name from live images
+                               $dbw->update(
+                                       'oldimage',
+                                       [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
+                                       [ 'oi_actor' => $actorId ],
+                                       __METHOD__
+                               );
+
+                               # Hide name from deleted images
+                               $dbw->update(
+                                       'filearchive',
+                                       [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
+                                       [ 'fa_actor' => $actorId ],
+                                       __METHOD__
+                               );
+                       }
+               }
+
+               # Hide log entries pointing to the user page
                $dbw->update(
                        'logging',
                        [ self::buildSetBitDeletedField( 'log_deleted', $op, $delAction, $dbw ) ],
                        [ 'log_namespace' => NS_USER, 'log_title' => $userDbKey,
-                               'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
+                       'log_type != ' . $dbw->addQuotes( 'suppress' ) ],
                        __METHOD__
                );
 
-               # Hide name from RC
-               $dbw->update(
-                       'recentchanges',
-                       [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delUser, $dbw ) ],
-                       [ 'rc_user_text' => $name ],
-                       __METHOD__
-               );
+               # Hide RC entries pointing to the user page
                $dbw->update(
                        'recentchanges',
                        [ self::buildSetBitDeletedField( 'rc_deleted', $op, $delAction, $dbw ) ],
@@ -109,21 +188,6 @@ class RevisionDeleteUser {
                        __METHOD__
                );
 
-               # Hide name from live images
-               $dbw->update(
-                       'oldimage',
-                       [ self::buildSetBitDeletedField( 'oi_deleted', $op, $delUser, $dbw ) ],
-                       [ 'oi_user_text' => $name ],
-                       __METHOD__
-               );
-
-               # Hide name from deleted images
-               $dbw->update(
-                       'filearchive',
-                       [ self::buildSetBitDeletedField( 'fa_deleted', $op, $delUser, $dbw ) ],
-                       [ 'fa_user_text' => $name ],
-                       __METHOD__
-               );
                # Done!
                return true;
        }
index d6f9578..d9fa82d 100644 (file)
@@ -463,6 +463,12 @@ class Command {
                                $isWrite = array_key_exists( $fd, $readPipes );
 
                                if ( $isWrite ) {
+                                       // Don't bother writing if the buffer is empty
+                                       if ( $buffers[$fd] === '' ) {
+                                               fclose( $pipes[$fd] );
+                                               unset( $pipes[$fd] );
+                                               continue;
+                                       }
                                        $res = fwrite( $pipe, $buffers[$fd], 65536 );
                                } else {
                                        $res = fread( $pipe, 65536 );
index 5aa1c6b..b8d7063 100644 (file)
@@ -121,7 +121,11 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                                'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
                                                        &$query_options, &$join_conds
                                                ) {
-                                                       $conds[] = 'rc_user = 0';
+                                                       $actorMigration = ActorMigration::newMigration();
+                                                       $actorQuery = $actorMigration->getJoin( 'rc_user' );
+                                                       $tables += $actorQuery['tables'];
+                                                       $join_conds += $actorQuery['joins'];
+                                                       $conds[] = $actorMigration->isAnon( $actorQuery['fields']['rc_user'] );
                                                },
                                                'isReplacedInStructuredUi' => true,
 
@@ -135,7 +139,11 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                                'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
                                                        &$query_options, &$join_conds
                                                ) {
-                                                       $conds[] = 'rc_user != 0';
+                                                       $actorMigration = ActorMigration::newMigration();
+                                                       $actorQuery = $actorMigration->getJoin( 'rc_user' );
+                                                       $tables += $actorQuery['tables'];
+                                                       $join_conds += $actorQuery['joins'];
+                                                       $conds[] = $actorMigration->isNotAnon( $actorQuery['fields']['rc_user'] );
                                                },
                                                'isReplacedInStructuredUi' => true,
                                        ]
@@ -220,8 +228,10 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                                'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
                                                        &$query_options, &$join_conds
                                                ) {
-                                                       $user = $ctx->getUser();
-                                                       $conds[] = 'rc_user_text != ' . $dbr->addQuotes( $user->getName() );
+                                                       $actorQuery = ActorMigration::newMigration()->getWhere( $dbr, 'rc_user', $ctx->getUser() );
+                                                       $tables += $actorQuery['tables'];
+                                                       $join_conds += $actorQuery['joins'];
+                                                       $conds[] = 'NOT(' . $actorQuery['conds'] . ')';
                                                },
                                                'cssClassSuffix' => 'self',
                                                'isRowApplicableCallable' => function ( $ctx, $rc ) {
@@ -236,8 +246,11 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                                                'queryCallable' => function ( $specialClassName, $ctx, $dbr, &$tables, &$fields, &$conds,
                                                        &$query_options, &$join_conds
                                                ) {
-                                                       $user = $ctx->getUser();
-                                                       $conds[] = 'rc_user_text = ' . $dbr->addQuotes( $user->getName() );
+                                                       $actorQuery = ActorMigration::newMigration()
+                                                               ->getWhere( $dbr, 'rc_user', $ctx->getUser(), false );
+                                                       $tables += $actorQuery['tables'];
+                                                       $join_conds += $actorQuery['joins'];
+                                                       $conds[] = $actorQuery['conds'];
                                                },
                                                'cssClassSuffix' => 'others',
                                                'isRowApplicableCallable' => function ( $ctx, $rc ) {
@@ -844,7 +857,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
         */
        protected function outputTimeout() {
                $this->getOutput()->addHTML(
-                       '<div class="mw-changeslist-timeout">' .
+                       '<div class="mw-changeslist-empty mw-changeslist-timeout">' .
                        $this->msg( 'recentchanges-timeout' )->parse() .
                        '</div>'
                );
@@ -1702,22 +1715,27 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                        return;
                }
 
+               $actorMigration = ActorMigration::newMigration();
+               $actorQuery = $actorMigration->getJoin( 'rc_user' );
+               $tables += $actorQuery['tables'];
+               $join_conds += $actorQuery['joins'];
+
                // 'registered' but not 'unregistered', experience levels, if any, are included in 'registered'
                if (
                        in_array( 'registered', $selectedExpLevels ) &&
                        !in_array( 'unregistered', $selectedExpLevels )
                ) {
-                       $conds[] = 'rc_user != 0';
+                       $conds[] = $actorMigration->isNotAnon( $actorQuery['fields']['rc_user'] );
                        return;
                }
 
                if ( $selectedExpLevels === [ 'unregistered' ] ) {
-                       $conds[] = 'rc_user = 0';
+                       $conds[] = $actorMigration->isAnon( $actorQuery['fields']['rc_user'] );
                        return;
                }
 
                $tables[] = 'user';
-               $join_conds['user'] = [ 'LEFT JOIN', 'rc_user = user_id' ];
+               $join_conds['user'] = [ 'LEFT JOIN', $actorQuery['fields']['rc_user'] . ' = user_id' ];
 
                if ( $now === 0 ) {
                        $now = time();
@@ -1747,7 +1765,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
 
                if ( in_array( 'unregistered', $selectedExpLevels ) ) {
                        $selectedExpLevels = array_diff( $selectedExpLevels, [ 'unregistered' ] );
-                       $conditions[] = 'rc_user = 0';
+                       $conditions[] = $actorMigration->isAnon( $actorQuery['fields']['rc_user'] );
                }
 
                if ( $selectedExpLevels === [ 'newcomer' ] ) {
@@ -1769,7 +1787,7 @@ abstract class ChangesListSpecialPage extends SpecialPage {
                } elseif ( $selectedExpLevels === [ 'experienced', 'learner' ] ) {
                        $conditions[] = $aboveNewcomer;
                } elseif ( $selectedExpLevels === [ 'experienced', 'learner', 'newcomer' ] ) {
-                       $conditions[] = 'rc_user != 0';
+                       $conditions[] = $actorMigration->isNotAnon( $actorQuery['fields']['rc_user'] );
                }
 
                if ( count( $conditions ) > 1 ) {
index 8021bc2..7694a61 100644 (file)
@@ -85,15 +85,17 @@ class FileDuplicateSearchPage extends QueryPage {
        }
 
        public function getQueryInfo() {
+               $imgQuery = LocalFile::getQueryInfo();
                return [
-                       'tables' => [ 'image' ],
+                       'tables' => $imgQuery['tables'],
                        'fields' => [
                                'title' => 'img_name',
                                'value' => 'img_sha1',
-                               'img_user_text',
+                               'img_user_text' => $imgQuery['fields']['img_user_text'],
                                'img_timestamp'
                        ],
-                       'conds' => [ 'img_sha1' => $this->hash ]
+                       'conds' => [ 'img_sha1' => $this->hash ],
+                       'join_conds' => $imgQuery['joins'],
                ];
        }
 
index cc43580..6a11bf4 100644 (file)
@@ -32,6 +32,8 @@ class SpecialLog extends SpecialPage {
        }
 
        public function execute( $par ) {
+               global $wgActorTableSchemaMigrationStage;
+
                $this->setHeaders();
                $this->outputHeader();
                $this->getOutput()->addModules( 'mediawiki.userSuggest' );
@@ -78,12 +80,33 @@ class SpecialLog extends SpecialPage {
                # Handle type-specific inputs
                $qc = [];
                if ( $opts->getValue( 'type' ) == 'suppress' ) {
-                       $offender = User::newFromName( $opts->getValue( 'offender' ), false );
+                       $offenderName = $opts->getValue( 'offender' );
+                       $offender = empty( $offenderName ) ? null : User::newFromName( $offenderName, false );
                        if ( $offender ) {
-                               if ( $offender->getId() > 0 ) {
-                                       $qc = [ 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ];
-                               } elseif ( !empty( $opts->getValue( 'offender' ) ) ) {
-                                       $qc = [ 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ];
+                               if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                                       $qc = [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ];
+                               } else {
+                                       if ( $offender->getId() > 0 ) {
+                                               $field = 'target_author_id';
+                                               $value = $offender->getId();
+                                       } else {
+                                               $field = 'target_author_ip';
+                                               $value = $offender->getName();
+                                       }
+                                       if ( !$offender->getActorId() ) {
+                                               $qc = [ 'ls_field' => $field, 'ls_value' => $value ];
+                                       } else {
+                                               $db = wfGetDB( DB_REPLICA );
+                                               $qc = [
+                                                       'ls_field' => [ 'target_author_actor', $field ], // So LogPager::getQueryInfo() works right
+                                                       $db->makeList( [
+                                                               $db->makeList(
+                                                                       [ 'ls_field' => 'target_author_actor', 'ls_value' => $offender->getActorId() ], LIST_AND
+                                                               ),
+                                                               $db->makeList( [ 'ls_field' => $field, 'ls_value' => $value ], LIST_AND ),
+                                                       ], LIST_OR ),
+                                               ];
+                                       }
                                }
                        }
                } else {
index 3290abd..a54d72d 100644 (file)
@@ -56,8 +56,9 @@ class MIMEsearchPage extends QueryPage {
                        // Allow wildcard searching
                        $minorType['img_minor_mime'] = $this->minor;
                }
+               $imgQuery = LocalFile::getQueryInfo();
                $qi = [
-                       'tables' => [ 'image' ],
+                       'tables' => $imgQuery['tables'],
                        'fields' => [
                                'namespace' => NS_FILE,
                                'title' => 'img_name',
@@ -67,7 +68,7 @@ class MIMEsearchPage extends QueryPage {
                                'img_size',
                                'img_width',
                                'img_height',
-                               'img_user_text',
+                               'img_user_text' => $imgQuery['fields']['img_user_text'],
                                'img_timestamp'
                        ],
                        'conds' => [
@@ -89,6 +90,7 @@ class MIMEsearchPage extends QueryPage {
                                        MEDIATYPE_3D,
                                ],
                        ] + $minorType,
+                       'join_conds' => $imgQuery['joins'],
                ];
 
                return $qi;
index ea0f0ed..46d5276 100644 (file)
@@ -299,6 +299,7 @@ class SpecialNewpages extends IncludableSpecialPage {
                        'deleted' => $result->rc_deleted,
                        'user_text' => $result->rc_user_text,
                        'user' => $result->rc_user,
+                       'actor' => $result->rc_actor,
                ], 0, $title );
        }
 
index d4aef6c..181b4db 100644 (file)
@@ -65,7 +65,6 @@ class SpecialRecentChangesLinked extends SpecialRecentChanges {
                        $outputPage->addHTML(
                                Html::errorBox( $this->msg( 'allpagesbadtitle' )->parse() )
                        );
-
                        return false;
                }
 
@@ -295,12 +294,19 @@ class SpecialRecentChangesLinked extends SpecialRecentChanges {
        }
 
        protected function outputNoResults() {
-               if ( $this->getTargetTitle() === false ) {
+               $targetTitle = $this->getTargetTitle();
+               if ( $targetTitle === false ) {
                        $this->getOutput()->addHTML(
-                               '<div class="mw-changeslist-notargetpage">' .
+                               '<div class="mw-changeslist-empty mw-changeslist-notargetpage">' .
                                $this->msg( 'recentchanges-notargetpage' )->parse() .
                                '</div>'
                        );
+               } elseif ( !$targetTitle || $targetTitle->isExternal() ) {
+                       $this->getOutput()->addHTML(
+                               '<div class="mw-changeslist-empty mw-changeslist-invalidtargetpage">' .
+                               $this->msg( 'allpagesbadtitle' )->parse() .
+                               '</div>'
+                       );
                } else {
                        parent::outputNoResults();
                }
index 3273046..36e7779 100644 (file)
@@ -177,11 +177,13 @@ class SpecialRedirect extends FormSpecialPage {
                        return null;
                }
 
+               $logQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
+
                $logparams = [
-                       'log_id',
-                       'log_timestamp',
-                       'log_type',
-                       'log_user_text',
+                       'log_id' => 'log_id',
+                       'log_timestamp' => 'log_timestamp',
+                       'log_type' => 'log_type',
+                       'log_user_text' => $logQuery['fields']['log_user_text'],
                ];
 
                $dbr = wfGetDB( DB_REPLICA );
@@ -197,9 +199,12 @@ class SpecialRedirect extends FormSpecialPage {
                // Returns all fields mentioned in $logparams of the logs
                // with the same timestamp as the one returned by the statement above
                $logsSameTimestamps = $dbr->select(
-                       'logging',
+                       [ 'logging' ] + $logQuery['tables'],
                        $logparams,
-                       [ "log_timestamp = ($inner)" ]
+                       [ "log_timestamp = ($inner)" ],
+                       __METHOD__,
+                       [],
+                       $logQuery['joins']
                );
                if ( $logsSameTimestamps->numRows() === 0 ) {
                        return null;
@@ -217,10 +222,10 @@ class SpecialRedirect extends FormSpecialPage {
 
                // Stores all the rows with the same values in each column
                // as $rowMain
-               foreach ( $logparams as $cond ) {
+               foreach ( $logparams as $key => $dummy ) {
                        $matchedRows = [];
                        foreach ( $logsSameTimestamps as $row ) {
-                               if ( $row->$cond === $rowMain->$cond ) {
+                               if ( $row->$key === $rowMain->$key ) {
                                        $matchedRows[] = $row;
                                }
                        }
@@ -238,7 +243,7 @@ class SpecialRedirect extends FormSpecialPage {
                        'log_user_text' => 'user'
                ];
 
-               foreach ( $logparams as $logKey ) {
+               foreach ( $logparams as $logKey => $dummy ) {
                        $query[$keys[$logKey]] = $matchedRows[0]->$logKey;
                }
                $query['offset'] = $query['offset'] + 1;
index 64af71a..26ed499 100644 (file)
@@ -79,14 +79,17 @@ class ActiveUsersPager extends UsersPager {
        function getQueryInfo() {
                $dbr = $this->getDatabase();
 
+               $rcQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
+
                $activeUserSeconds = $this->getConfig()->get( 'ActiveUserDays' ) * 86400;
                $timestamp = $dbr->timestamp( wfTimestamp( TS_UNIX ) - $activeUserSeconds );
-               $tables = [ 'querycachetwo', 'user', 'recentchanges' ];
+               $tables = [ 'querycachetwo', 'user', 'recentchanges' ] + $rcQuery['tables'];
+               $jconds = $rcQuery['joins'];
                $conds = [
                        'qcc_type' => 'activeusers',
                        'qcc_namespace' => NS_USER,
                        'user_name = qcc_title',
-                       'rc_user_text = qcc_title',
+                       $rcQuery['fields']['rc_user_text'] . ' = qcc_title',
                        'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Don't count wikidata.
                        'rc_type != ' . $dbr->addQuotes( RC_CATEGORIZE ), // Don't count categorization changes.
                        'rc_log_type IS NULL OR rc_log_type != ' . $dbr->addQuotes( 'newusers' ),
@@ -127,7 +130,8 @@ class ActiveUsersPager extends UsersPager {
                                'recentedits' => 'COUNT(*)'
                        ],
                        'options' => [ 'GROUP BY' => [ 'qcc_title' ] ],
-                       'conds' => $conds
+                       'conds' => $conds,
+                       'join_conds' => $jconds,
                ];
        }
 
index fe7cac0..cac0736 100644 (file)
@@ -210,15 +210,16 @@ class BlockListPager extends TablePager {
 
        function getQueryInfo() {
                $commentQuery = CommentStore::getStore()->getJoin( 'ipb_reason' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'ipb_by' );
 
                $info = [
-                       'tables' => [ 'ipblocks', 'user' ] + $commentQuery['tables'],
+                       'tables' => array_merge(
+                               [ 'ipblocks' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ]
+                       ),
                        'fields' => [
                                'ipb_id',
                                'ipb_address',
                                'ipb_user',
-                               'ipb_by',
-                               'ipb_by_text',
                                'by_user_name' => 'user_name',
                                'ipb_timestamp',
                                'ipb_auto',
@@ -231,9 +232,11 @@ class BlockListPager extends TablePager {
                                'ipb_deleted',
                                'ipb_block_email',
                                'ipb_allow_usertalk',
-                       ] + $commentQuery['fields'],
+                       ] + $commentQuery['fields'], $actorQuery['fields'],
                        'conds' => $this->conds,
-                       'join_conds' => [ 'user' => [ 'LEFT JOIN', 'user_id = ipb_by' ] ] + $commentQuery['joins']
+                       'join_conds' => [
+                               'user' => [ 'LEFT JOIN', 'user_id = ' . $actorQuery['fields']['ipb_by'] ]
+                       ] + $commentQuery['joins'] + $actorQuery['joins']
                ];
 
                # Filter out any expired blocks
index e29467d..6fdf05f 100644 (file)
@@ -186,7 +186,7 @@ class ContribsPager extends RangeChronologicalPager {
 
                if ( $this->contribs == 'newbie' ) {
                        $max = $this->mDb->selectField( 'user', 'max(user_id)', false, __METHOD__ );
-                       $queryInfo['conds'][] = 'rev_user >' . (int)( $max - $max / 100 );
+                       $queryInfo['conds'][] = $revQuery['fields']['rev_user'] . ' >' . (int)( $max - $max / 100 );
                        # ignore local groups with the bot right
                        # @todo FIXME: Global groups may have 'bot' rights
                        $groupsWithBotPermission = User::getGroupsWithPermission( 'bot' );
@@ -195,7 +195,7 @@ class ContribsPager extends RangeChronologicalPager {
                                $queryInfo['conds'][] = 'ug_group IS NULL';
                                $queryInfo['join_conds']['user_groups'] = [
                                        'LEFT JOIN', [
-                                               'ug_user = rev_user',
+                                               'ug_user = ' . $revQuery['fields']['rev_user'],
                                                'ug_group' => $groupsWithBotPermission,
                                                'ug_expiry IS NULL OR ug_expiry >= ' .
                                                        $this->mDb->addQuotes( $this->mDb->timestamp() )
@@ -208,23 +208,18 @@ class ContribsPager extends RangeChronologicalPager {
                        $queryInfo['conds'][] = 'rev_timestamp > ' .
                                $this->mDb->addQuotes( $this->mDb->timestamp( wfTimestamp() - 30 * 24 * 60 * 60 ) );
                } else {
-                       $uid = User::idFromName( $this->target );
-                       if ( $uid ) {
-                               $queryInfo['conds']['rev_user'] = $uid;
-                               $queryInfo['options']['USE INDEX']['revision'] = 'user_timestamp';
+                       $user = User::newFromName( $this->target, false );
+                       $ipRangeConds = $user->isAnon() ? $this->getIpRangeConds( $this->mDb, $this->target ) : null;
+                       if ( $ipRangeConds ) {
+                               $queryInfo['tables'][] = 'ip_changes';
+                               $queryInfo['join_conds']['ip_changes'] = [
+                                       'LEFT JOIN', [ 'ipc_rev_id = rev_id' ]
+                               ];
+                               $queryInfo['conds'][] = $ipRangeConds;
                        } else {
-                               $ipRangeConds = $this->getIpRangeConds( $this->mDb, $this->target );
-
-                               if ( $ipRangeConds ) {
-                                       $queryInfo['tables'][] = 'ip_changes';
-                                       $queryInfo['join_conds']['ip_changes'] = [
-                                               'LEFT JOIN', [ 'ipc_rev_id = rev_id' ]
-                                       ];
-                                       $queryInfo['conds'][] = $ipRangeConds;
-                               } else {
-                                       $queryInfo['conds']['rev_user_text'] = $this->target;
-                                       $queryInfo['options']['USE INDEX']['revision'] = 'usertext_timestamp';
-                               }
+                               // tables and joins are already handled by Revision::getQueryInfo()
+                               $queryInfo['conds'][] = ActorMigration::newMigration()
+                                       ->getWhere( $this->mDb, 'rev_user', $user )['conds'];
                        }
                }
 
index 1c31724..d642e66 100644 (file)
@@ -58,7 +58,12 @@ class DeletedContribsPager extends IndexPager {
        }
 
        function getQueryInfo() {
-               list( $index, $userCond ) = $this->getUserCond();
+               $userCond = [
+                       // ->getJoin() below takes care of any joins needed
+                       ActorMigration::newMigration()->getWhere(
+                               wfGetDB( DB_REPLICA ), 'ar_user', User::newFromName( $this->target, false ), false
+                       )['conds']
+               ];
                $conds = array_merge( $userCond, $this->getNamespaceCond() );
                $user = $this->getUser();
                // Paranoia: avoid brute force searches (T19792)
@@ -70,16 +75,17 @@ class DeletedContribsPager extends IndexPager {
                }
 
                $commentQuery = CommentStore::getStore()->getJoin( 'ar_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'ar_user' );
 
                return [
-                       'tables' => [ 'archive' ] + $commentQuery['tables'],
+                       'tables' => [ 'archive' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        'fields' => [
                                'ar_rev_id', 'ar_namespace', 'ar_title', 'ar_timestamp',
-                               'ar_minor_edit', 'ar_user', 'ar_user_text', 'ar_deleted'
-                       ] + $commentQuery['fields'],
+                               'ar_minor_edit', 'ar_deleted'
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
                        'conds' => $conds,
-                       'options' => [ 'USE INDEX' => [ 'archive' => $index ] ],
-                       'join_conds' => $commentQuery['joins'],
+                       'options' => [],
+                       'join_conds' => $commentQuery['joins'] + $actorQuery['joins'],
                ];
        }
 
@@ -128,15 +134,6 @@ class DeletedContribsPager extends IndexPager {
                return new FakeResultWrapper( $result );
        }
 
-       function getUserCond() {
-               $condition = [];
-
-               $condition['ar_user_text'] = $this->target;
-               $index = 'ar_usertext_timestamp';
-
-               return [ $index, $condition ];
-       }
-
        function getIndexField() {
                return 'ar_timestamp';
        }
@@ -259,6 +256,7 @@ class DeletedContribsPager extends IndexPager {
                        'comment' => CommentStore::getStore()->getComment( 'ar_comment', $row )->text,
                        'user' => $row->ar_user,
                        'user_text' => $row->ar_user_text,
+                       'actor' => isset( $row->ar_actor ) ? $row->ar_actor : null,
                        'timestamp' => $row->ar_timestamp,
                        'minor_edit' => $row->ar_minor_edit,
                        'deleted' => $row->ar_deleted,
index ae19d59..75c2f77 100644 (file)
@@ -193,9 +193,9 @@ class ImageListPager extends TablePager {
                }
                $sortable = [ 'img_timestamp', 'img_name', 'img_size' ];
                /* For reference, the indicies we can use for sorting are:
-                * On the image table: img_user_timestamp, img_usertext_timestamp,
+                * On the image table: img_user_timestamp/img_usertext_timestamp/img_actor_timestamp,
                 * img_size, img_timestamp
-                * On oldimage: oi_usertext_timestamp, oi_name_timestamp
+                * On oldimage: oi_usertext_timestamp/oi_actor_timestamp, oi_name_timestamp
                 *
                 * In particular that means we cannot sort by timestamp when not filtering
                 * by user and including old images in the results. Which is sad.
@@ -246,6 +246,7 @@ class ImageListPager extends TablePager {
                $tables = [ $table ];
                $fields = $this->getFieldNames();
                unset( $fields['img_description'] );
+               unset( $fields['img_user_text'] );
                $fields = array_keys( $fields );
 
                if ( $table === 'oldimage' ) {
@@ -261,7 +262,6 @@ class ImageListPager extends TablePager {
                                $fields[array_search( 'top', $fields )] = "'yes' AS top";
                        }
                }
-               $fields[] = $prefix . '_user AS img_user';
                $fields[array_search( 'thumb', $fields )] = $prefix . '_name AS thumb';
 
                $options = $join_conds = [];
@@ -273,6 +273,14 @@ class ImageListPager extends TablePager {
                $join_conds += $commentQuery['joins'];
                $fields['description_field'] = "'{$prefix}_description'";
 
+               # User fields
+               $actorQuery = ActorMigration::newMigration()->getJoin( $prefix . '_user' );
+               $tables += $actorQuery['tables'];
+               $join_conds += $actorQuery['joins'];
+               $fields['img_user'] = $actorQuery['fields'][$prefix . '_user'];
+               $fields['img_user_text'] = $actorQuery['fields'][$prefix . '_user_text'];
+               $fields['img_actor'] = $actorQuery['fields'][$prefix . '_actor'];
+
                # Depends on $wgMiserMode
                # Will also not happen if mShowAll is true.
                if ( isset( $this->mFieldNames['count'] ) ) {
@@ -287,7 +295,7 @@ class ImageListPager extends TablePager {
                        unset( $field );
 
                        $columnlist = preg_grep( '/^img/', array_keys( $this->getFieldNames() ) );
-                       $options = [ 'GROUP BY' => array_merge( [ 'img_user' ], $columnlist ) ];
+                       $options = [ 'GROUP BY' => array_merge( [ $fields['img_user'] ], $columnlist ) ];
                        $join_conds['oldimage'] = [ 'LEFT JOIN', 'oi_name = img_name' ];
                }
 
index 001c296..4815189 100644 (file)
@@ -59,26 +59,24 @@ class NewFilesPager extends RangeChronologicalPager {
 
        function getQueryInfo() {
                $opts = $this->opts;
-               $conds = $jconds = [];
-               $tables = [ 'image' ];
-               $fields = [ 'img_name', 'img_user', 'img_timestamp' ];
+               $conds = [];
+               $imgQuery = LocalFile::getQueryInfo();
+               $tables = $imgQuery['tables'];
+               $fields = [ 'img_name', 'img_timestamp' ] + $imgQuery['fields'];
                $options = [];
+               $jconds = $imgQuery['joins'];
 
                $user = $opts->getValue( 'user' );
                if ( $user !== '' ) {
-                       $userId = User::idFromName( $user );
-                       if ( $userId ) {
-                               $conds['img_user'] = $userId;
-                       } else {
-                               $conds['img_user_text'] = $user;
-                       }
+                       $conds[] = ActorMigration::newMigration()
+                               ->getWhere( wfGetDB( DB_REPLICA ), 'img_user', User::newFromName( $user, false ) )['conds'];
                }
 
                if ( $opts->getValue( 'newbies' ) ) {
                        // newbie = most recent 1% of users
                        $dbr = wfGetDB( DB_REPLICA );
                        $max = $dbr->selectField( 'user', 'max(user_id)', false, __METHOD__ );
-                       $conds[] = 'img_user >' . (int)( $max - $max / 100 );
+                       $conds[] = $imgQuery['fields']['img_user'] . ' >' . (int)( $max - $max / 100 );
 
                        // there's no point in looking for new user activity in a far past;
                        // beyond a certain point, we'd just end up scanning the rest of the
@@ -99,7 +97,7 @@ class NewFilesPager extends RangeChronologicalPager {
                                        'LEFT JOIN',
                                        [
                                                'ug_group' => $groupsWithBotPermission,
-                                               'ug_user = img_user',
+                                               'ug_user = ' . $imgQuery['fields']['img_user'],
                                                'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() )
                                        ]
                                ];
@@ -107,16 +105,27 @@ class NewFilesPager extends RangeChronologicalPager {
                }
 
                if ( $opts->getValue( 'hidepatrolled' ) ) {
+                       global $wgActorTableSchemaMigrationStage;
+
                        $tables[] = 'recentchanges';
                        $conds['rc_type'] = RC_LOG;
                        $conds['rc_log_type'] = 'upload';
                        $conds['rc_patrolled'] = 0;
                        $conds['rc_namespace'] = NS_FILE;
+
+                       if ( $wgActorTableSchemaMigrationStage === MIGRATION_NEW ) {
+                               $jcond = 'rc_actor = ' . $imgQuery['fields']['img_actor'];
+                       } else {
+                               $rcQuery = ActorMigration::newMigration()->getJoin( 'rc_user' );
+                               $tables += $rcQuery['tables'];
+                               $joins += $rcQuery['joins'];
+                               $jcond = $rcQuery['fields']['rc_user'] . ' = ' . $imgQuery['fields']['img_user'];
+                       }
                        $jconds['recentchanges'] = [
                                'INNER JOIN',
                                [
                                        'rc_title = img_name',
-                                       'rc_user = img_user',
+                                       $jcond,
                                        'rc_timestamp = img_timestamp'
                                ]
                        ];
index 61b648d..9746334 100644 (file)
@@ -39,6 +39,8 @@ class NewPagesPager extends ReverseChronologicalPager {
        }
 
        function getQueryInfo() {
+               $rcQuery = RecentChange::getQueryInfo();
+
                $conds = [];
                $conds['rc_new'] = 1;
 
@@ -68,13 +70,14 @@ class NewPagesPager extends ReverseChronologicalPager {
                }
 
                if ( $user ) {
-                       $conds['rc_user_text'] = $user->getText();
-                       $rcIndexes = 'rc_user_text';
+                       $conds[] = ActorMigration::newMigration()->getWhere(
+                               $this->mDb, 'rc_user', User::newFromName( $user->getText(), false ), false
+                       )['conds'];
                } elseif ( User::groupHasPermission( '*', 'createpage' ) &&
                        $this->opts->getValue( 'hideliu' )
                ) {
                        # If anons cannot make new pages, don't "exclude logged in users"!
-                       $conds['rc_user'] = 0;
+                       $conds[] = ActorMigration::newMigration()->isAnon( $rcQuery['fields']['rc_user'] );
                }
 
                # If this user cannot see patrolled edits or they are off, don't do dumb queries!
@@ -90,17 +93,12 @@ class NewPagesPager extends ReverseChronologicalPager {
                        $conds['page_is_redirect'] = 0;
                }
 
-               $commentQuery = CommentStore::getStore()->getJoin( 'rc_comment' );
-
                // Allow changes to the New Pages query
-               $tables = [ 'recentchanges', 'page' ] + $commentQuery['tables'];
+               $tables = array_merge( $rcQuery['tables'], [ 'page' ] );
                $fields = [
-                       'rc_namespace', 'rc_title', 'rc_cur_id', 'rc_user', 'rc_user_text',
-                       'rc_timestamp', 'rc_patrolled', 'rc_id', 'rc_deleted',
-                       'length' => 'page_len', 'rev_id' => 'page_latest', 'rc_this_oldid',
-                       'page_namespace', 'page_title'
-               ] + $commentQuery['fields'];
-               $join_conds = [ 'page' => [ 'INNER JOIN', 'page_id=rc_cur_id' ] ] + $commentQuery['joins'];
+                       'length' => 'page_len', 'rev_id' => 'page_latest', 'page_namespace', 'page_title'
+               ] + $rcQuery['fields'];
+               $join_conds = [ 'page' => [ 'INNER JOIN', 'page_id=rc_cur_id' ] ] + $rcQuery['joins'];
 
                // Avoid PHP 7.1 warning from passing $this by reference
                $pager = $this;
index c4ea5f8..3b69698 100644 (file)
@@ -284,9 +284,13 @@ class ProtectedPagesPager extends TablePager {
                }
 
                $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
 
                return [
-                       'tables' => [ 'page', 'page_restrictions', 'log_search', 'logging' ] + $commentQuery['tables'],
+                       'tables' => [
+                               'page', 'page_restrictions', 'log_search',
+                               'logparen' => [ 'logging' ] + $commentQuery['tables'] + $actorQuery['tables'],
+                       ],
                        'fields' => [
                                'pr_id',
                                'page_namespace',
@@ -297,9 +301,8 @@ class ProtectedPagesPager extends TablePager {
                                'pr_expiry',
                                'pr_cascade',
                                'log_timestamp',
-                               'log_user',
                                'log_deleted',
-                       ] + $commentQuery['fields'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
                        'conds' => $conds,
                        'join_conds' => [
                                'log_search' => [
@@ -307,12 +310,12 @@ class ProtectedPagesPager extends TablePager {
                                                'ls_field' => 'pr_id', 'ls_value = ' . $this->mDb->buildStringCast( 'pr_id' )
                                        ]
                                ],
-                               'logging' => [
+                               'logparen' => [
                                        'LEFT JOIN', [
                                                'ls_log_id = log_id'
                                        ]
                                ]
-                       ] + $commentQuery['joins']
+                       ] + $commentQuery['joins'] + $actorQuery['joins']
                ];
        }
 
index eeade49..869adca 100644 (file)
@@ -31,6 +31,7 @@ use Wikimedia\IPSet;
 use Wikimedia\ScopedCallback;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\DBExpectedError;
+use Wikimedia\Rdbms\IDatabase;
 
 /**
  * String Some punctuation to prevent editing from broken text-mangling proxies.
@@ -70,7 +71,7 @@ class User implements IDBAccessObject, UserIdentity {
        /**
         * @const int Serialized record version.
         */
-       const VERSION = 11;
+       const VERSION = 12;
 
        /**
         * Exclude user options that are set to their default value.
@@ -111,6 +112,8 @@ class User implements IDBAccessObject, UserIdentity {
                'mGroupMemberships',
                // user_properties table
                'mOptionOverrides',
+               // actor table
+               'mActorId',
        ];
 
        /**
@@ -207,6 +210,8 @@ class User implements IDBAccessObject, UserIdentity {
        public $mId;
        /** @var string */
        public $mName;
+       /** @var int|null */
+       protected $mActorId;
        /** @var string */
        public $mRealName;
 
@@ -251,6 +256,7 @@ class User implements IDBAccessObject, UserIdentity {
         *  - 'defaults'   anonymous user initialised from class defaults
         *  - 'name'       initialise from mName
         *  - 'id'         initialise from mId
+        *  - 'actor'      initialise from mActorId
         *  - 'session'    log in from session if possible
         *
         * Use the User::newFrom*() family of functions to set this.
@@ -309,6 +315,7 @@ class User implements IDBAccessObject, UserIdentity {
         *
         * @see newFromName()
         * @see newFromId()
+        * @see newFromActorId()
         * @see newFromConfirmationCode()
         * @see newFromSession()
         * @see newFromRow()
@@ -401,6 +408,32 @@ class User implements IDBAccessObject, UserIdentity {
                        case 'id':
                                $this->loadFromId( $flags );
                                break;
+                       case 'actor':
+                               // Make sure this thread sees its own changes
+                               if ( wfGetLB()->hasOrMadeRecentMasterChanges() ) {
+                                       $flags |= self::READ_LATEST;
+                                       $this->queryFlagsUsed = $flags;
+                               }
+
+                               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $flags );
+                               $row = wfGetDB( $index )->selectRow(
+                                       'actor',
+                                       [ 'actor_user', 'actor_name' ],
+                                       [ 'actor_id' => $this->mActorId ],
+                                       __METHOD__,
+                                       $options
+                               );
+
+                               if ( !$row ) {
+                                       // Ugh.
+                                       $this->loadDefaults();
+                               } elseif ( $row->actor_user ) {
+                                       $this->mId = $row->actor_user;
+                                       $this->loadFromId( $flags );
+                               } else {
+                                       $this->loadDefaults( $row->actor_name );
+                               }
+                               break;
                        case 'session':
                                if ( !$this->loadFromSession() ) {
                                        // Loading from session failed. Load defaults.
@@ -575,6 +608,78 @@ class User implements IDBAccessObject, UserIdentity {
                return $u;
        }
 
+       /**
+        * Static factory method for creation from a given actor ID.
+        *
+        * @since 1.31
+        * @param int $id Valid actor ID
+        * @return User The corresponding User object
+        */
+       public static function newFromActorId( $id ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) {
+                       throw new BadMethodCallException(
+                               'Cannot use ' . __METHOD__ . ' when $wgActorTableSchemaMigrationStage is MIGRATION_OLD'
+                       );
+               }
+
+               $u = new User;
+               $u->mActorId = $id;
+               $u->mFrom = 'actor';
+               $u->setItemLoaded( 'actor' );
+               return $u;
+       }
+
+       /**
+        * Static factory method for creation from an ID, name, and/or actor ID
+        *
+        * This does not check that the ID, name, and actor ID all correspond to
+        * the same user.
+        *
+        * @since 1.31
+        * @param int|null $userId User ID, if known
+        * @param string|null $userName User name, if known
+        * @param int|null $actorId Actor ID, if known
+        * @return User
+        */
+       public static function newFromAnyId( $userId, $userName, $actorId ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               $user = new User;
+               $user->mFrom = 'defaults';
+
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD && $actorId !== null ) {
+                       $user->mActorId = (int)$actorId;
+                       if ( $user->mActorId !== 0 ) {
+                               $user->mFrom = 'actor';
+                       }
+                       $user->setItemLoaded( 'actor' );
+               }
+
+               if ( $userName !== null && $userName !== '' ) {
+                       $user->mName = $userName;
+                       $user->mFrom = 'name';
+                       $user->setItemLoaded( 'name' );
+               }
+
+               if ( $userId !== null ) {
+                       $user->mId = (int)$userId;
+                       if ( $user->mId !== 0 ) {
+                               $user->mFrom = 'id';
+                       }
+                       $user->setItemLoaded( 'id' );
+               }
+
+               if ( $user->mFrom === 'defaults' ) {
+                       throw new InvalidArgumentException(
+                               'Cannot create a user with no name, no ID, and no actor ID'
+                       );
+               }
+
+               return $user;
+       }
+
        /**
         * Factory method to fetch whichever user has a given email confirmation code.
         * This code is generated when an account is created or its e-mail address
@@ -1164,6 +1269,7 @@ class User implements IDBAccessObject, UserIdentity {
        public function loadDefaults( $name = false ) {
                $this->mId = 0;
                $this->mName = $name;
+               $this->mActorId = null;
                $this->mRealName = '';
                $this->mEmail = '';
                $this->mOptionOverrides = null;
@@ -1320,11 +1426,29 @@ class User implements IDBAccessObject, UserIdentity {
         *  user_properties   Array with properties out of the user_properties table
         */
        protected function loadFromRow( $row, $data = null ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               if ( !is_object( $row ) ) {
+                       throw new InvalidArgumentException( '$row must be an object' );
+               }
+
                $all = true;
 
                $this->mGroupMemberships = null; // deferred
 
-               if ( isset( $row->user_name ) ) {
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       if ( isset( $row->actor_id ) ) {
+                               $this->mActorId = (int)$row->actor_id;
+                               if ( $this->mActorId !== 0 ) {
+                                       $this->mFrom = 'actor';
+                               }
+                               $this->setItemLoaded( 'actor' );
+                       } else {
+                               $all = false;
+                       }
+               }
+
+               if ( isset( $row->user_name ) && $row->user_name !== '' ) {
                        $this->mName = $row->user_name;
                        $this->mFrom = 'name';
                        $this->setItemLoaded( 'name' );
@@ -1341,13 +1465,15 @@ class User implements IDBAccessObject, UserIdentity {
 
                if ( isset( $row->user_id ) ) {
                        $this->mId = intval( $row->user_id );
-                       $this->mFrom = 'id';
+                       if ( $this->mId !== 0 ) {
+                               $this->mFrom = 'id';
+                       }
                        $this->setItemLoaded( 'id' );
                } else {
                        $all = false;
                }
 
-               if ( isset( $row->user_id ) && isset( $row->user_name ) ) {
+               if ( isset( $row->user_id ) && isset( $row->user_name ) && $row->user_name !== '' ) {
                        self::$idCacheByName[$row->user_name] = $row->user_id;
                }
 
@@ -1555,7 +1681,7 @@ class User implements IDBAccessObject, UserIdentity {
         * data (i.e. self::$mCacheVars) is not cleared unless $reloadFrom is given.
         *
         * @param bool|string $reloadFrom Reload user and user_groups table data from a
-        *   given source. May be "name", "id", "defaults", "session", or false for no reload.
+        *   given source. May be "name", "id", "actor", "defaults", "session", or false for no reload.
         */
        public function clearInstanceCache( $reloadFrom = false ) {
                $this->mNewtalk = -1;
@@ -2284,6 +2410,67 @@ class User implements IDBAccessObject, UserIdentity {
                $this->mName = $str;
        }
 
+       /**
+        * Get the user's actor ID.
+        * @since 1.31
+        * @param IDatabase|null $dbw Assign a new actor ID, using this DB handle, if none exists
+        * @return int The actor's ID, or 0 if no actor ID exists and $dbw was null
+        */
+       public function getActorId( IDatabase $dbw = null ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_OLD ) {
+                       return 0;
+               }
+
+               if ( !$this->isItemLoaded( 'actor' ) ) {
+                       $this->load();
+               }
+
+               // Currently $this->mActorId might be null if $this was loaded from a
+               // cache entry that was written when $wgActorTableSchemaMigrationStage
+               // was MIGRATION_OLD. Once that is no longer a possibility (i.e. when
+               // User::VERSION is incremented after $wgActorTableSchemaMigrationStage
+               // has been removed), that condition may be removed.
+               if ( $this->mActorId === null || !$this->mActorId && $dbw ) {
+                       $q = [
+                               'actor_user' => $this->getId() ?: null,
+                               'actor_name' => (string)$this->getName(),
+                       ];
+                       if ( $dbw ) {
+                               if ( $q['actor_user'] === null && self::isUsableName( $q['actor_name'] ) ) {
+                                       throw new CannotCreateActorException(
+                                               'Cannot create an actor for a usable name that is not an existing user'
+                                       );
+                               }
+                               if ( $q['actor_name'] === '' ) {
+                                       throw new CannotCreateActorException( 'Cannot create an actor for a user with no name' );
+                               }
+                               $dbw->insert( 'actor', $q, __METHOD__, [ 'IGNORE' ] );
+                               if ( $dbw->affectedRows() ) {
+                                       $this->mActorId = (int)$dbw->insertId();
+                               } else {
+                                       // Outdated cache?
+                                       list( , $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
+                                       $this->mActorId = (int)$dbw->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
+                                       if ( !$this->mActorId ) {
+                                               throw new CannotCreateActorException(
+                                                       "Cannot create actor ID for user_id={$this->getId()} user_name={$this->getName()}"
+                                               );
+                                       }
+                               }
+                               $this->invalidateCache();
+                       } else {
+                               list( $index, $options ) = DBAccessObjectUtils::getDBOptions( $this->queryFlagsUsed );
+                               $db = wfGetDB( $index );
+                               $this->mActorId = (int)$db->selectField( 'actor', 'actor_id', $q, __METHOD__, $options );
+                       }
+                       $this->setItemLoaded( 'actor' );
+               }
+
+               return (int)$this->mActorId;
+       }
+
        /**
         * Get the user's name escaped by underscores.
         * @return string Username escaped by underscores.
@@ -4040,31 +4227,40 @@ class User implements IDBAccessObject, UserIdentity {
                $newTouched = $this->newTouchedTimestamp();
 
                $dbw = wfGetDB( DB_MASTER );
-               $dbw->update( 'user',
-                       [ /* SET */
-                               'user_name' => $this->mName,
-                               'user_real_name' => $this->mRealName,
-                               'user_email' => $this->mEmail,
-                               'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
-                               'user_touched' => $dbw->timestamp( $newTouched ),
-                               'user_token' => strval( $this->mToken ),
-                               'user_email_token' => $this->mEmailToken,
-                               'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
-                       ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
-                               'user_id' => $this->mId,
-                       ] ), __METHOD__
-               );
+               $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $newTouched ) {
+                       $dbw->update( 'user',
+                               [ /* SET */
+                                       'user_name' => $this->mName,
+                                       'user_real_name' => $this->mRealName,
+                                       'user_email' => $this->mEmail,
+                                       'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
+                                       'user_touched' => $dbw->timestamp( $newTouched ),
+                                       'user_token' => strval( $this->mToken ),
+                                       'user_email_token' => $this->mEmailToken,
+                                       'user_email_token_expires' => $dbw->timestampOrNull( $this->mEmailTokenExpires ),
+                               ], $this->makeUpdateConditions( $dbw, [ /* WHERE */
+                                       'user_id' => $this->mId,
+                               ] ), $fname
+                       );
 
-               if ( !$dbw->affectedRows() ) {
-                       // Maybe the problem was a missed cache update; clear it to be safe
-                       $this->clearSharedCache( 'refresh' );
-                       // User was changed in the meantime or loaded with stale data
-                       $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
-                       throw new MWException(
-                               "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
-                               " the version of the user to be saved is older than the current version."
+                       if ( !$dbw->affectedRows() ) {
+                               // Maybe the problem was a missed cache update; clear it to be safe
+                               $this->clearSharedCache( 'refresh' );
+                               // User was changed in the meantime or loaded with stale data
+                               $from = ( $this->queryFlagsUsed & self::READ_LATEST ) ? 'master' : 'replica';
+                               throw new MWException(
+                                       "CAS update failed on user_touched for user ID '{$this->mId}' (read from $from);" .
+                                       " the version of the user to be saved is older than the current version."
+                               );
+                       }
+
+                       $dbw->update(
+                               'actor',
+                               [ 'actor_name' => $this->mName ],
+                               [ 'actor_user' => $this->mId ],
+                               $fname
                        );
-               }
+               } );
 
                $this->mTouched = $newTouched;
                $this->saveOptions();
@@ -4149,13 +4345,19 @@ class User implements IDBAccessObject, UserIdentity {
                foreach ( $params as $name => $value ) {
                        $fields["user_$name"] = $value;
                }
-               $dbw->insert( 'user', $fields, __METHOD__, [ 'IGNORE' ] );
-               if ( $dbw->affectedRows() ) {
-                       $newUser = self::newFromId( $dbw->insertId() );
-               } else {
-                       $newUser = null;
-               }
-               return $newUser;
+
+               return $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) use ( $fields ) {
+                       $dbw->insert( 'user', $fields, $fname, [ 'IGNORE' ] );
+                       if ( $dbw->affectedRows() ) {
+                               $newUser = self::newFromId( $dbw->insertId() );
+                               $newUser->mName = $fields['user_name'];
+                               $newUser->setItemLoaded( 'name' );
+                               $newUser->updateActorId( $dbw );
+                       } else {
+                               $newUser = null;
+                       }
+                       return $newUser;
+               } );
        }
 
        /**
@@ -4196,55 +4398,79 @@ class User implements IDBAccessObject, UserIdentity {
 
                $this->mTouched = $this->newTouchedTimestamp();
 
-               $noPass = PasswordFactory::newInvalidPassword()->toString();
-
                $dbw = wfGetDB( DB_MASTER );
-               $dbw->insert( 'user',
-                       [
-                               'user_name' => $this->mName,
-                               'user_password' => $noPass,
-                               'user_newpassword' => $noPass,
-                               'user_email' => $this->mEmail,
-                               'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
-                               'user_real_name' => $this->mRealName,
-                               'user_token' => strval( $this->mToken ),
-                               'user_registration' => $dbw->timestamp( $this->mRegistration ),
-                               'user_editcount' => 0,
-                               'user_touched' => $dbw->timestamp( $this->mTouched ),
-                       ], __METHOD__,
-                       [ 'IGNORE' ]
-               );
-               if ( !$dbw->affectedRows() ) {
-                       // Use locking reads to bypass any REPEATABLE-READ snapshot.
-                       $this->mId = $dbw->selectField(
-                               'user',
-                               'user_id',
-                               [ 'user_name' => $this->mName ],
-                               __METHOD__,
-                               [ 'LOCK IN SHARE MODE' ]
+               $status = $dbw->doAtomicSection( __METHOD__, function ( $dbw, $fname ) {
+                       $noPass = PasswordFactory::newInvalidPassword()->toString();
+                       $dbw->insert( 'user',
+                               [
+                                       'user_name' => $this->mName,
+                                       'user_password' => $noPass,
+                                       'user_newpassword' => $noPass,
+                                       'user_email' => $this->mEmail,
+                                       'user_email_authenticated' => $dbw->timestampOrNull( $this->mEmailAuthenticated ),
+                                       'user_real_name' => $this->mRealName,
+                                       'user_token' => strval( $this->mToken ),
+                                       'user_registration' => $dbw->timestamp( $this->mRegistration ),
+                                       'user_editcount' => 0,
+                                       'user_touched' => $dbw->timestamp( $this->mTouched ),
+                               ], $fname,
+                               [ 'IGNORE' ]
                        );
-                       $loaded = false;
-                       if ( $this->mId ) {
-                               if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
-                                       $loaded = true;
+                       if ( !$dbw->affectedRows() ) {
+                               // Use locking reads to bypass any REPEATABLE-READ snapshot.
+                               $this->mId = $dbw->selectField(
+                                       'user',
+                                       'user_id',
+                                       [ 'user_name' => $this->mName ],
+                                       __METHOD__,
+                                       [ 'LOCK IN SHARE MODE' ]
+                               );
+                               $loaded = false;
+                               if ( $this->mId ) {
+                                       if ( $this->loadFromDatabase( self::READ_LOCKING ) ) {
+                                               $loaded = true;
+                                       }
                                }
+                               if ( !$loaded ) {
+                                       throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
+                                               "to insert user '{$this->mName}' row, but it was not present in select!" );
+                               }
+                               return Status::newFatal( 'userexists' );
                        }
-                       if ( !$loaded ) {
-                               throw new MWException( __METHOD__ . ": hit a key conflict attempting " .
-                                       "to insert user '{$this->mName}' row, but it was not present in select!" );
-                       }
-                       return Status::newFatal( 'userexists' );
+                       $this->mId = $dbw->insertId();
+                       self::$idCacheByName[$this->mName] = $this->mId;
+                       $this->updateActorId( $dbw );
+
+                       return Status::newGood();
+               } );
+               if ( !$status->isGood() ) {
+                       return $status;
                }
-               $this->mId = $dbw->insertId();
-               self::$idCacheByName[$this->mName] = $this->mId;
 
-               // Clear instance cache other than user table data, which is already accurate
+               // Clear instance cache other than user table data and actor, which is already accurate
                $this->clearInstanceCache();
 
                $this->saveOptions();
                return Status::newGood();
        }
 
+       /**
+        * Update the actor ID after an insert
+        * @param IDatabase $dbw Writable database handle
+        */
+       private function updateActorId( IDatabase $dbw ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       $dbw->insert(
+                               'actor',
+                               [ 'actor_user' => $this->mId, 'actor_name' => $this->mName ],
+                               __METHOD__
+                       );
+                       $this->mActorId = (int)$dbw->insertId();
+               }
+       }
+
        /**
         * If this user is logged-in and blocked,
         * block any IP address they've successfully logged in from.
@@ -4733,10 +4959,14 @@ class User implements IDBAccessObject, UserIdentity {
                        return false; // anons
                }
                $dbr = wfGetDB( DB_REPLICA );
-               $time = $dbr->selectField( 'revision', 'rev_timestamp',
-                       [ 'rev_user' => $this->getId() ],
+               $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
+               $time = $dbr->selectField(
+                       [ 'revision' ] + $actorWhere['tables'],
+                       'rev_timestamp',
+                       [ $actorWhere['conds'] ],
                        __METHOD__,
-                       [ 'ORDER BY' => 'rev_timestamp ASC' ]
+                       [ 'ORDER BY' => 'rev_timestamp ASC' ],
+                       $actorWhere['joins']
                );
                if ( !$time ) {
                        return false; // no edits
@@ -5184,11 +5414,14 @@ class User implements IDBAccessObject, UserIdentity {
                // Pull from a replica DB to be less cruel to servers
                // Accuracy isn't the point anyway here
                $dbr = wfGetDB( DB_REPLICA );
+               $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
                $count = (int)$dbr->selectField(
-                       'revision',
-                       'COUNT(rev_user)',
-                       [ 'rev_user' => $this->getId() ],
-                       __METHOD__
+                       [ 'revision' ] + $actorWhere['tables'],
+                       'COUNT(*)',
+                       [ $actorWhere['conds'] ],
+                       __METHOD__,
+                       [],
+                       $actorWhere['joins']
                );
                $count = $count + $add;
 
@@ -5537,7 +5770,9 @@ class User implements IDBAccessObject, UserIdentity {
         *   - joins: (array) to include in the `$join_conds` to `IDatabase->select()`
         */
        public static function getQueryInfo() {
-               return [
+               global $wgActorTableSchemaMigrationStage;
+
+               $ret = [
                        'tables' => [ 'user' ],
                        'fields' => [
                                'user_id',
@@ -5554,6 +5789,15 @@ class User implements IDBAccessObject, UserIdentity {
                        ],
                        'joins' => [],
                ];
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       $ret['tables']['user_actor'] = 'actor';
+                       $ret['fields'][] = 'user_actor.actor_id';
+                       $ret['joins']['user_actor'] = [
+                               $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                               [ 'user_actor.actor_user = user_id' ]
+                       ];
+               }
+               return $ret;
        }
 
        /**
index 57a0408..d02a678 100644 (file)
@@ -45,7 +45,13 @@ interface UserIdentity {
         */
        public function getName();
 
-       // TODO: in the future, we should also provide access to the actor ID here.
+       /**
+        * @since 1.31
+        *
+        * @return int The user's actor ID. May be 0 if no actor ID is set.
+        */
+       public function getActorId();
+
        // TODO: we may want to (optionally?) provide a global ID, see CentralIdLookup.
 
 }
index e728264..120f31f 100644 (file)
@@ -41,16 +41,24 @@ class UserIdentityValue implements UserIdentity {
         */
        private $name;
 
+       /**
+        * @var int
+        */
+       private $actor;
+
        /**
         * @param int $id
         * @param string $name
+        * @param int $actor
         */
-       public function __construct( $id, $name ) {
+       public function __construct( $id, $name, $actor ) {
                Assert::parameterType( 'integer', $id, '$id' );
                Assert::parameterType( 'string', $name, '$name' );
+               Assert::parameterType( 'integer', $actor, '$actor' );
 
                $this->id = $id;
                $this->name = $name;
+               $this->actor = $actor;
        }
 
        /**
@@ -67,4 +75,11 @@ class UserIdentityValue implements UserIdentity {
                return $this->name;
        }
 
+       /**
+        * @return int The user's actor ID. May be 0 if no actor ID has been assigned.
+        */
+       public function getActorId() {
+               return $this->actor;
+       }
+
 }
index abe9b89..412fdf5 100644 (file)
@@ -59,12 +59,17 @@ class WatchedItemQueryService {
        /** @var CommentStore */
        private $commentStore;
 
+       /** @var ActorMigration */
+       private $actorMigration;
+
        public function __construct(
                LoadBalancer $loadBalancer,
-               CommentStore $commentStore
+               CommentStore $commentStore,
+               ActorMigration $actorMigration
        ) {
                $this->loadBalancer = $loadBalancer;
                $this->commentStore = $commentStore;
+               $this->actorMigration = $actorMigration;
        }
 
        /**
@@ -335,6 +340,14 @@ class WatchedItemQueryService {
                if ( in_array( self::INCLUDE_TAGS, $options['includeFields'] ) ) {
                        $tables[] = 'tag_summary';
                }
+               if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ||
+                       in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ||
+                       in_array( self::FILTER_ANON, $options['filters'] ) ||
+                       in_array( self::FILTER_NOT_ANON, $options['filters'] ) ||
+                       array_key_exists( 'onlyByUser', $options ) || array_key_exists( 'notByUser', $options )
+               ) {
+                       $tables += $this->actorMigration->getJoin( 'rc_user' )['tables'];
+               }
                return $tables;
        }
 
@@ -367,10 +380,10 @@ class WatchedItemQueryService {
                        $fields = array_merge( $fields, [ 'rc_type', 'rc_minor', 'rc_bot' ] );
                }
                if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ) {
-                       $fields[] = 'rc_user_text';
+                       $fields['rc_user_text'] = $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user_text'];
                }
                if ( in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ) {
-                       $fields[] = 'rc_user';
+                       $fields['rc_user'] = $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user'];
                }
                if ( in_array( self::INCLUDE_COMMENT, $options['includeFields'] ) ) {
                        $fields += $this->commentStore->getJoin( 'rc_comment' )['fields'];
@@ -469,9 +482,13 @@ class WatchedItemQueryService {
                }
 
                if ( in_array( self::FILTER_ANON, $options['filters'] ) ) {
-                       $conds[] = 'rc_user = 0';
+                       $conds[] = $this->actorMigration->isAnon(
+                               $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user']
+                       );
                } elseif ( in_array( self::FILTER_NOT_ANON, $options['filters'] ) ) {
-                       $conds[] = 'rc_user != 0';
+                       $conds[] = $this->actorMigration->isNotAnon(
+                               $this->actorMigration->getJoin( 'rc_user' )['fields']['rc_user']
+                       );
                }
 
                if ( $user->useRCPatrol() || $user->useNPPatrol() ) {
@@ -523,9 +540,11 @@ class WatchedItemQueryService {
                $conds = [];
 
                if ( array_key_exists( 'onlyByUser', $options ) ) {
-                       $conds['rc_user_text'] = $options['onlyByUser'];
+                       $byUser = User::newFromName( $options['onlyByUser'], false );
+                       $conds[] = $this->actorMigration->getWhere( $db, 'rc_user', $byUser )['conds'];
                } elseif ( array_key_exists( 'notByUser', $options ) ) {
-                       $conds[] = 'rc_user_text != ' . $db->addQuotes( $options['notByUser'] );
+                       $byUser = User::newFromName( $options['notByUser'], false );
+                       $conds[] = 'NOT(' . $this->actorMigration->getWhere( $db, 'rc_user', $byUser )['conds'] . ')';
                }
 
                // Avoid brute force searches (T19342)
@@ -685,6 +704,14 @@ class WatchedItemQueryService {
                if ( in_array( self::INCLUDE_TAGS, $options['includeFields'] ) ) {
                        $joinConds['tag_summary'] = [ 'LEFT JOIN', [ 'rc_id=ts_rc_id' ] ];
                }
+               if ( in_array( self::INCLUDE_USER, $options['includeFields'] ) ||
+                       in_array( self::INCLUDE_USER_ID, $options['includeFields'] ) ||
+                       in_array( self::FILTER_ANON, $options['filters'] ) ||
+                       in_array( self::FILTER_NOT_ANON, $options['filters'] ) ||
+                       array_key_exists( 'onlyByUser', $options ) || array_key_exists( 'notByUser', $options )
+               ) {
+                       $joinConds += $this->actorMigration->getJoin( 'rc_user' )['joins'];
+               }
                return $joinConds;
        }
 
index 6966832..6fb4544 100644 (file)
                "resources/src/mediawiki.special",
                "resources/src/mediawiki.toolbar",
                "resources/src/mediawiki.widgets",
-               "resources/src/mediawiki.widgets.visibleByteLimit",
+               "resources/src/mediawiki.widgets.visibleLengthLimit",
                "resources/src/jquery/jquery.accessKeyLabel.js",
                "resources/src/jquery/jquery.byteLength.js",
-               "resources/src/jquery/jquery.byteLimit.js",
                "resources/src/jquery/jquery.checkboxShiftClick.js",
                "resources/src/jquery/jquery.colorUtil.js",
                "resources/src/jquery/jquery.confirmable.js",
                "resources/src/jquery/jquery.footHovzer.js",
                "resources/src/jquery/jquery.getAttrs.js",
                "resources/src/jquery/jquery.hidpi.js",
+               "resources/src/jquery/jquery.lengthLimit.js",
                "resources/src/jquery/jquery.localize.js",
                "resources/src/jquery/jquery.makeCollapsible.js",
                "resources/src/jquery/jquery.spinner.js",
index 56513ff..d449f5b 100644 (file)
        "userjspreview": "'''تذكر أنك فقط تجرب/تعاين جافاسكربت.'''\n'''لم يتم الحفظ بعد!'''",
        "sitecsspreview": "''' تذكر أنك فقط في وضع المعاينة لهذا CSS ''' \n''' ولم يتم حفظ الصفحة بعد! '''",
        "sitejspreview": "''' تذكر أنك فقط في وضع المعاينة لكود JavaScript هذا''' \n''' ولم يتم حفظه بعد! '''",
-       "userinvalidcssjstitle": "'''تحذير:''' لا توجد واجهة  \"$1\".\nتذكر أن ملفات ال.css و ال.js تستخدم حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''تحذير:''' لا توجد واجهة  \"$1\".\nتذكر أن ملفات ال.css و ال.js تستخدم حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
        "updated": "(محدثة)",
        "note": "'''ملاحظة:'''",
        "previewnote": "'''تذكر أن هذه مجرد معاينة أولية.'''\nلم تحفظ تغييراتك إلى الآن!",
index c467a1e..0710d40 100644 (file)
        "userjspreview": "'''Onthou hierdie is slegs 'n toets/voorskou van u gebruiker-JavaScript, dit is nog nie gestoor nie.'''",
        "sitecsspreview": "'''Onthou dat u na 'n voorskou van die CSS-kode kyk.'''\n'''Dit is nog nie gestoor nie!'''",
        "sitejspreview": "'''Onthou dat u na 'n voorskou van die JavaScript-kode kyk.'''\n'''Dit is nog nie gestoor nie!'''",
-       "userinvalidcssjstitle": "'''Waarskuwing:''' daar is nie 'n omslag \"$1\" nie.\nOnthou dat u eie .css- en .js-bladsye met 'n kleinletter begin, byvoorbeeld {{ns:user}}:Naam/vector.css in plaas van {{ns:user}}:Naam/Vector.css.",
+       "userinvalidconfigtitle": "'''Waarskuwing:''' daar is nie 'n omslag \"$1\" nie.\nOnthou dat u eie .css- en .js-bladsye met 'n kleinletter begin, byvoorbeeld {{ns:user}}:Naam/vector.css in plaas van {{ns:user}}:Naam/Vector.css.",
        "updated": "(Gewysig)",
        "note": "'''Nota:'''",
        "previewnote": "'''Onthou dat hierdie slegs 'n voorskou is.'''\nU teks is nog nie gestoor nie!",
        "prefs-files": "Lêers",
        "prefs-custom-css": "Persoonlike CSS",
        "prefs-custom-js": "Persoonlike JS",
-       "prefs-common-css-js": "Gedeelde CSS/JS vir al die omslae:",
+       "prefs-common-config": "Gedeelde CSS/JS vir al die omslae:",
        "prefs-reset-intro": "U kan die blad gebruik om u voorkeure terug te stel na die webwerf se verstekwaardes.\nDie aksie kan nie ongedaan gemaak word nie.",
        "prefs-emailconfirm-label": "E-posbevestiging:",
        "youremail": "E-posadres:",
index adae8ab..cc7b50c 100644 (file)
        "userjspreview": "strong>imhini pataayaway miazih kisu numisuay misaungayay a JavaScript.\nJavaScript caay henay misuped!</strong>",
        "sitecsspreview": "<strong>imhini kisu ayza i pataayaway miazih tina CSS, CSS caay henay suped!</strong>",
        "sitejspreview": "<strong> mipataayaway miazih tina JavaScript kisu ayza, JavaScript caay henay misuped!</strong>",
-       "userinvalidcssjstitle": "<strong>patalaw:</strong> inayi’ tina nuhekalan yangse \"$1\".\npakuniza misanga’ a .css atu .js kasabelih maydih pisaungay adidi’ay ku sulit satangahan, tinaku:{{ns:user}}:Foo/vector.css atu {{ns:user}}:Foo/Vector.css caay kalecad",
+       "userinvalidconfigtitle": "<strong>patalaw:</strong> inayi’ tina nuhekalan yangse \"$1\".\npakuniza misanga’ a .css atu .js kasabelih maydih pisaungay adidi’ay ku sulit satangahan, tinaku:{{ns:user}}:Foo/vector.css atu {{ns:user}}:Foo/Vector.css caay kalecad",
        "updated": "(misabaluh tuway)",
        "note": "<strong>azihen:</strong>",
        "previewnote": "<strong>imahini kisu mapataayaway miazih, misu a pisumad caay henay kasinga’</strong>",
        "prefs-files": "tangan",
        "prefs-custom-css": "pakuniza misanga’ CSS",
        "prefs-custom-js": "pakuniza misanga’ JavaScript",
-       "prefs-common-css-js": "sacahamin nuhekalan kapulungan a CSS/JavaScript:",
+       "prefs-common-config": "sacahamin nuhekalan kapulungan a CSS/JavaScript:",
        "prefs-reset-intro": "kapah kisu misaungay tina kasabelih miliyaw patizeng tunumisu misetin tu kanamuhan mala calay-kakacawan(wangcan) pataayaw sulyang.\ntina pisaungay a la’cus panukasan.",
        "prefs-emailconfirm-label": "imyiyo(email) malucekay tuway:",
        "youremail": "imyiyo(email):",
index 28265fc..9b570e5 100644 (file)
        "userjsyoucanpreview": "'''Këshillë:''' Përdorni butonin 'Trego parapâmjen' për me testue JS para se me i regjistrue ndryshimet.",
        "usercsspreview": "'''Vini re, jeni tue pâ veç parapâmjen e CSSit tuej.'''\n'''Ende nuk e keni ruejtë!'''",
        "userjspreview": "<strong>Vini re, jeni duke testuar/parë vetëm parapamjen e JavaScriptit tuaj.\nEnde nuk e keni ruajtur!</strong>",
-       "userinvalidcssjstitle": "'''Kujdes:''' Nuk ka pâmje me emën \"$1\".\nVini re që faqet .css dhe .js përdorin vetëm titull me germa të vogla, psh. {{ns:user}}:Foo/vector.css për dallim prej {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Kujdes:''' Nuk ka pâmje me emën \"$1\".\nVini re që faqet .css dhe .js përdorin vetëm titull me germa të vogla, psh. {{ns:user}}:Foo/vector.css për dallim prej {{ns:user}}:Foo/Vector.css.",
        "updated": "(E ndryshueme)",
        "note": "'''Shenim:'''",
        "previewnote": "'''Kjo asht vetëm parapamje.'''\nNdryshimet e tua nuk janë ruejtë ende!",
index 7128e15..4b386e5 100644 (file)
        "userjspreview": "'''ማስታወሻ፦ JavaScriptዎን ለሙከራ ብቻ እያዩ ነው፤ ገና አልተቆጠበም!'''",
        "sitecsspreview": "'''ማስታወሻ፦ CSSዎን ለሙከራ ብቻ እያዩ ነው፤ ገና አልተቆጠበም!'''",
        "sitejspreview": "'''ማስታወሻ፦ JavaScriptዎን ለሙከራ ብቻ እያዩ ነው፤ ገና አልተቆጠበም!'''",
-       "userinvalidcssjstitle": "'''ማስጠንቀቂያ፦''' «$1» የሚባል መልክ የለም። ልዩ .css እና .js ገጾች በትንንሽ እንግሊዝኛ ፊደል መጀመር እንዳለባቸው ያስታውሱ። ለምሳሌ፦  {{ns:user}}:Foo/vector.css ልክ ነው እንጂ {{ns:user}}:Foo/Vector.css አይደለም።",
+       "userinvalidconfigtitle": "'''ማስጠንቀቂያ፦''' «$1» የሚባል መልክ የለም። ልዩ .css እና .js ገጾች በትንንሽ እንግሊዝኛ ፊደል መጀመር እንዳለባቸው ያስታውሱ። ለምሳሌ፦  {{ns:user}}:Foo/vector.css ልክ ነው እንጂ {{ns:user}}:Foo/Vector.css አይደለም።",
        "updated": "(የታደሰ)",
        "note": "'''ማሳሰቢያ፦'''",
        "previewnote": "ማስታወቂያ፦ '''ይህ ለሙከራ ብቻ ነው የሚታየው፣ ምንም ለውጦች ገና አልተላኩም!'''",
index 792b9e0..d30a0d0 100644 (file)
        "userjspreview": "'''Remere que sólo ye previsualizando o suyo javascript d'usuario y encara no ye grabato!'''",
        "sitecsspreview": "'''Remere que isto no ye soque previsualizando iste CSS.'''\n'''Encara no s'ha alzato!'''",
        "sitejspreview": "'''Remere que isto no ye soque previsualizando iste codigo de JavaScript.'''\n'''Encara no s'ha alzato!'''",
-       "userinvalidcssjstitle": "'''Pare cuenta:''' No bi ha garra aparencia clamata \"$1\". Remere que as pachinas presonalizatas .css y .js tienen un títol en minusclas, p.e. {{ns:user}}:Foo/vector.css en cuenta de {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Pare cuenta:''' No bi ha garra aparencia clamata \"$1\". Remere que as pachinas presonalizatas .css y .js tienen un títol en minusclas, p.e. {{ns:user}}:Foo/vector.css en cuenta de {{ns:user}}:Foo/Vector.css.",
        "updated": "(Esviellato)",
        "note": "'''Nota:'''",
        "previewnote": "'''Pare cuenta que isto no ye que l'anvista previa.''' Os cambeos encara no s'ha alzato!",
        "prefs-files": "fichers",
        "prefs-custom-css": "CSS presonalizato",
        "prefs-custom-js": "JS presonalizato",
-       "prefs-common-css-js": "CSS/JS compartito ta todas as apariencias:",
+       "prefs-common-config": "CSS/JS compartito ta todas as apariencias:",
        "prefs-reset-intro": "Puet emplegar ista pachina ta restaurar as suyas preferencias a las valuras por defecto d'o sitio.\nNo se podrá desfer iste cambio.",
        "prefs-emailconfirm-label": "Confirmación de correu electronico:",
        "youremail": "Adreza de correu electronico:",
index 0062d99..6cbdbaf 100644 (file)
        "userjspreview": "'''تذكر أنك فقط تجرب/تعاين جافاسكربت.'''\n'''لم يتم الحفظ بعد!'''",
        "sitecsspreview": "''' تذكر أنك فقط في وضع المعاينة لهذا CSS ''' \n''' ولم يتم حفظ الصفحة بعد! '''",
        "sitejspreview": "''' تذكر أنك فقط في وضع المعاينة لكود JavaScript هذا''' \n''' ولم يتم حفظه بعد! '''",
-       "userinvalidcssjstitle": "'''تحذير:''' لا توجد واجهة  \"$1\".\nتذكر أن ملفات ال.css و ال.js تستخدم حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''تحذير:''' لا توجد واجهة  \"$1\".\nتذكر أن ملفات ال.css و ال.js تستخدم حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
        "updated": "(محدثة)",
        "note": "'''ملاحظة:'''",
        "previewnote": "'''تذكر أن هذه مجرد معاينة أولية.'''\nلم تحفظ تغييراتك إلى الآن!",
        "prefs-files": "ملفات",
        "prefs-custom-css": "CSS مخصص",
        "prefs-custom-js": "جافاسكربت مخصص",
-       "prefs-common-css-js": "CSS وجافاسكربت مشترك لجميع الواجهات:",
+       "prefs-common-config": "CSS وجافاسكربت مشترك لجميع الواجهات:",
        "prefs-reset-intro": "يمكنك استخدام هذه الصفحة لإعادة تفضيلاتك للحالة الافتراضية للموقع.\nلن تستطيع استرجاع الحالة السابقة.",
        "prefs-emailconfirm-label": "تأكيد البريد الإلكتروني:",
        "youremail": "البريد:",
index 38003ef..f841ab9 100644 (file)
        "userjspreview": "'''تذكر أنك فقط تجرب/تعاين جافاسكربت.'''\n'''مازال ما  صراش التسجيل!'''",
        "sitecsspreview": "'''تفكر أنك راك تعرض الأنماط المتراصة (CSS) الخاصة بيك فقط\nو تمش حفظها بعد!'''",
        "sitejspreview": "'''تذكر أنك فقط تجرب/تعاين جافاسكربت.'''\n'''مازال ما  صراش التسجيل!'''",
-       "userinvalidcssjstitle": "'''تحذير:''' ما كانش تلبيسة\"$1\".\nتفكر بلي ملفات ال.css و ال.js تستعمل حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''تحذير:''' ما كانش تلبيسة\"$1\".\nتفكر بلي ملفات ال.css و ال.js تستعمل حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
        "updated": "ميزاجور (تحديث)",
        "note": "'''ملاحظة:'''",
        "previewnote": "'''ما تنساش هذي معاينه قبليه.'''\nلدوك التبديلات دياولك ما تسجلوش!",
index 367890f..4c48f6f 100644 (file)
        "userjspreview": "'''ma tnsa-ċ billa JavaScript ṫaĝk mazal f-mrḫalt l-iĝdad o-ṫjriba.'''\n'''rah mazal ma ṫsjjal-ċ!'''",
        "sitecsspreview": "'''ma tnsa-ċ billa CSS ṫaĝk mazal ġir f-mrḫalt l-iĝdad.'''\n'''rah mazal ma ṫsjjal-ċ!'''",
        "sitejspreview": "'''ma tnsa-ċ billa JavaScript ṫaĝk mazal ġir f-mrḫalt l-iĝdad.'''\n'''rah mazal ma ṫsjjal-ċ!'''",
-       "userinvalidcssjstitle": "'''ṫḫdir:''' ma kayn ḫṫṫa ċi skin \"$1\".\nṣfaḫi .css o-.js l-moĥṣṣaṣa ĥddama b-ĝanawin minuscule, bḫal {{ns:user}}:Foo/vector.css o-maċi bḫal {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''ṫḫdir:''' ma kayn ḫṫṫa ċi skin \"$1\".\nṣfaḫi .css o-.js l-moĥṣṣaṣa ĥddama b-ĝanawin minuscule, bḫal {{ns:user}}:Foo/vector.css o-maċi bḫal {{ns:user}}:Foo/Vector.css.",
        "updated": "(mohdata)",
        "note": "'''molahada:'''",
        "previewnote": "'''Dir fe balek belli hadċi ġir prévizualizasyon.'''\nDakċi li beddelṫi mazal ma ṫċejjel !",
        "prefs-files": "milffat",
        "prefs-custom-css": "personalisé CSS",
        "prefs-custom-js": "personalisé JavaScript",
-       "prefs-common-css-js": "CSS/JavaScript l-moċṫarak bin jmiĝ s-skinaṫ:",
+       "prefs-common-config": "CSS/JavaScript l-moċṫarak bin jmiĝ s-skinaṫ:",
        "prefs-emailconfirm-label": "konfirmi l'email:",
        "youremail": "I-Méyl",
        "username": "smiṫ l-mosṫĥdim:",
index b7d65d2..36e3e8a 100644 (file)
        "userjsyoucanpreview": "'''ملاحظة:''' استعمل زرار \"{{int:showpreview}}\" علشان تجرب النمط (CSS) أو الجافا سكريبت الجديد قبل تسييڤ الصفحه.",
        "usercsspreview": "'''افتكر انك  بتعرض  (CSS) بتاع اليوزر بس.\nهى لسه ماتسييڤتش!'''",
        "userjspreview": "'''أفتكر أنك بس بتجرب/بتعرض الجافا سكريبت بتاع اليوزر بتاعك، و انها لسة ماتحفظتش!'''",
-       "userinvalidcssjstitle": "'''تحذير:'''مافيش واجهة  \"$1\".\nافتكر أن ملفات ال.css و ال.js بتستخدم حروف صغيرة فى العنوان ، مثلا {{ns:user}}:Foo/vector.css و مش {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''تحذير:'''مافيش واجهة  \"$1\".\nافتكر أن ملفات ال.css و ال.js بتستخدم حروف صغيرة فى العنوان ، مثلا {{ns:user}}:Foo/vector.css و مش {{ns:user}}:Foo/Vector.css.",
        "updated": "(متحدثة)",
        "note": "'''ملحوظه:'''",
        "previewnote": "'''دى بروفه للصفحه بس.'''\nولسه ما تسييفتش! ،",
index 43e7591..8be15c8 100644 (file)
        "userjspreview": "'''মনত ৰাখিব আপুনি আপোনাৰ ব্যৱহাৰকাৰী জাভালিপি কেৱল পৰীক্ষা/প্ৰাকদৰ্শন কৰিছে ।'''\n '''এইটো এতিয়াও সাঁচি ৰখা হোৱা নাই ।'''",
        "sitecsspreview": "'''মনত ৰাখিব আপুনি কেৱল এইটো CSS  প্ৰাকদৰ্শন কৰিছে ।''' \n'''এইটো এতিয়াও সাঁচি ৰখা হোৱা নাই !'''",
        "sitejspreview": "'''মনত ৰাখিব আপুনি এই জাভালিপি ক’ড কেৱল প্ৰাকদৰ্শন কৰিছে ।'''\n '''এইটো এতিয়াও সাঁচি ৰখা হোৱা নাই !'''",
-       "userinvalidcssjstitle": "'''সতৰ্কবাণী:'''  \"$1\" নামৰ কোনো আৱৰণ নাই। Custom .css আৰু .js পৃষ্ঠাই সৰুফলাৰ শিৰোনামা ব্যৱহাৰ কৰে, যেনে-  {{ns:user}}:Foo/Vector.css ৰ সলনি {{ns:user}}:Foo/vector.css।",
+       "userinvalidconfigtitle": "'''সতৰ্কবাণী:'''  \"$1\" নামৰ কোনো আৱৰণ নাই। Custom .css আৰু .js পৃষ্ঠাই সৰুফলাৰ শিৰোনামা ব্যৱহাৰ কৰে, যেনে-  {{ns:user}}:Foo/Vector.css ৰ সলনি {{ns:user}}:Foo/vector.css।",
        "updated": "(আপডেট কৰা হ'ল)",
        "note": "'''টোকা:'''",
        "previewnote": "'''মনত ৰাখিব এয়া মাথোন প্ৰাক্‌দৰ্শনহে।'''\nআপোনাৰ সালসলনিসমূহ এতিয়াও সংৰক্ষণ কৰা হোৱা নাই!",
        "prefs-files": "ফাইলসমূহ",
        "prefs-custom-css": "স্বনিৰ্ধাৰিত CSS",
        "prefs-custom-js": "স্বনিৰ্ধাৰিত জাভা লিপি",
-       "prefs-common-css-js": "সকলো আৱৰণৰ বাবে উমৈহতীয়া চি.এচ.এচ./জাভালিপি",
+       "prefs-common-config": "সকলো আৱৰণৰ বাবে উমৈহতীয়া চি.এচ.এচ./জাভালিপি",
        "prefs-reset-intro": "আপুনি এই পৃষ্ঠা ব্যৱহাৰ কৰি আপোনাৰ পছন্দসমূহক চাইটৰ পূৰ্বনিৰ্ধাৰিত ছেটঙলৈ ঘূৰাই নিব পাৰে ।\nএই পৰিৱৰ্তন পিছত সলাব পৰা নাযাব ।",
        "prefs-emailconfirm-label": "ইমেইল নিশ্চিতকৰণ:",
        "youremail": "আপোনাৰ ই-মেইল *",
index 258e0c1..992d7b0 100644 (file)
        "userjspreview": "'''Recuerda que namái ye la prueba/vista previa del JavaScript d'usuariu.'''\n'''¡Inda nun ta guardáu!'''",
        "sitecsspreview": "'''Recuerda que namái tas previsualizando esti CSS.'''\n'''¡Tovía nun ta guardáu!'''",
        "sitejspreview": "'''Recuerda que namái tas probando esti códigu JavaScript.'''\n'''¡Inda nun tá guardáu!'''",
-       "userinvalidcssjstitle": "'''Avisu:''' Nun esiste'l tema «$1».\nLes páxines personalizaes de .css y .js usen un títulu en minúscules, p. ex. {{ns:user}}:Foo/vector.css y non {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Avisu:''' Nun esiste'l tema «$1».\nLes páxines personalizaes de .css y .js usen un títulu en minúscules, p. ex. {{ns:user}}:Foo/vector.css y non {{ns:user}}:Foo/Vector.css.",
        "updated": "(Anovao)",
        "note": "'''Nota:'''",
        "previewnote": "'''Alcuerdate de qu'esto ye sólo una vista previa.'''\n¡Los cambios entá nun se guardaron!",
        "prefs-files": "Ficheros",
        "prefs-custom-css": "CSS personalizáu",
        "prefs-custom-js": "JavaScript personalizáu",
-       "prefs-common-css-js": "CSS/JavaScript compartíu pa toles pieles:",
+       "prefs-common-config": "CSS/JavaScript compartíu pa toles pieles:",
        "prefs-reset-intro": "Pues usar esta páxina pa reaniciar les preferencies a los valores predeterminaos del sitiu.\nEsto nun se pue desfacer.",
        "prefs-emailconfirm-label": "Confirmación del corréu:",
        "youremail": "Corréu electrónicu:",
index 6849379..0db063c 100644 (file)
        "userjspreview": "'''ध्यान दिहा जाय कि आप आपन जावास्क्रिप्ट  कय झलक देखा जात है।'''\n'''इ अभीन तक नाई सहेजा है!'''",
        "sitecsspreview": "'''ध्यान दिहा जाय कि आप ई सी॰एस॰एस कय झलक देखा जात है।'''\n'''इ अभीन तक नाई सहेजा है!'''",
        "sitejspreview": "'''ध्यान दिहा जाय कि आप ई जावास्क्रिप्ट कय झलक देखा जात है।'''\n'''इ अभीन तक नाई सहेजा है!'''",
-       "userinvalidcssjstitle": "'''चेतावनी:''' \"$1\" नाव कय कवनो त्वचा नाइ है।\nबदलल .css औ .js पन्नन कय शीर्षक नीचे स्तर कय लिपि (lowercase) कय प्रयोग करत है। उदाहरण: {{ns:user}}:Foo/vector.css नाई की {{ns:user}}:Foo/Vector.css",
+       "userinvalidconfigtitle": "'''चेतावनी:''' \"$1\" नाव कय कवनो त्वचा नाइ है।\nबदलल .css औ .js पन्नन कय शीर्षक नीचे स्तर कय लिपि (lowercase) कय प्रयोग करत है। उदाहरण: {{ns:user}}:Foo/vector.css नाई की {{ns:user}}:Foo/Vector.css",
        "updated": "(अपडेट करल)",
        "note": "'''सूचना:'''",
        "previewnote": "'''याद रख्खा जाय, ई खाली एक झलक होय।'''\nआप कय बदलाव अभीन तक नाई सहेजा है!",
        "prefs-files": "फ़ाइल",
        "prefs-custom-css": "खासमखास सी॰एस॰एस",
        "prefs-custom-js": "खासमखास जावास्क्रिप्ट",
-       "prefs-common-css-js": "कुल स्किन कय लिए साझा सी॰एस॰एस/जावास्क्रिप्ट:",
+       "prefs-common-config": "कुल स्किन कय लिए साझा सी॰एस॰एस/जावास्क्रिप्ट:",
        "prefs-reset-intro": "आप इ पन्ना से अपने पसंद कय, साइट कय मूल पसंद कय जइसन बनाय सका जात है।\nएकरे बाद आप वापिस पुरान जगहि पे नाइ आय पावा जाइ।",
        "prefs-emailconfirm-label": "ईमेल सुनीश्चित करा जाय:",
        "youremail": "ई-मेल:",
index f428e9c..8d4c0d7 100644 (file)
        "prefs-files": "Fayllar",
        "prefs-custom-css": "Xüsusi CSS",
        "prefs-custom-js": "Xüsusi JavaScript",
-       "prefs-common-css-js": "Bütün skinlər üçün ümumi CSS/JavaScript:",
+       "prefs-common-config": "Bütün skinlər üçün ümumi CSS/JavaScript:",
        "prefs-reset-intro": "Bu səhifəni nizamlamalarınızı ilkin vəziyyətə gətirmək üçün istifadə edə bilərsiniz. Bu əməliyyat geri qaytarıla bilməz.",
        "prefs-emailconfirm-label": "E-poçtun təsdiqlənməsi:",
        "youremail": "E-məktub:",
index 3137638..7e8ba3b 100644 (file)
        "userjspreview": "'خاتیرلادیریق کی، سیز یالنیز سی اس اس -ده تئست/سیناق گؤستریشی ائتمیسینیز.'\n'بو هله یادداشدا ساخلانیلماییب!'",
        "sitecsspreview": "'''خاتیرلادیریق کی، سیز یالنیز سی‌اس‌اس-ده سیناق گؤستریشی ائتمیسینیز.'''\n'''بو هله یادداشدا ساخلانیلماییب!'''",
        "sitejspreview": "'خاتیرلادیریق کی، سیز یالنیز ژاواسجریپت کودوندا سیناق گؤستریشی ائتمیسینیز.'\n'بو هله یادداشدا ساخلانیلماییب!'",
-       "userinvalidcssjstitle": "خاتیرلادیرق:' '\"$1\" آدییلا بیر پوشه یوخ‌دور. پوشه-آدی.css و. js فایل‌لارینین آدلاری کیچیک حرف ایله یازماسی لازیم‌دیر، یعنی {{ns:user}}: تمل / vector.css دئییل، {{ns:user}}: تمل / Vector.css.",
+       "userinvalidconfigtitle": "خاتیرلادیرق:' '\"$1\" آدییلا بیر پوشه یوخ‌دور. پوشه-آدی.css و. js فایل‌لارینین آدلاری کیچیک حرف ایله یازماسی لازیم‌دیر، یعنی {{ns:user}}: تمل / vector.css دئییل، {{ns:user}}: تمل / Vector.css.",
        "updated": "(گونجل‌لندی)",
        "note": "'''دیقت:'''",
        "previewnote": "<strong>بونون تکجه بیر سیناق گؤستریشی اولدوغونو نظرده آلین.</strong>\nسیزین دییشیکلرینیز هله ذخیره اولونماییب!",
        "prefs-files": "فایل‌لار",
        "prefs-custom-css": "شخصی سی‌اس‌اس",
        "prefs-custom-js": "شخصی جاوااسکریپت",
-       "prefs-common-css-js": "بوتون قابیقلار اوچون پایلاشمیش سی‌اس‌اس/جاوااسکریپت:",
+       "prefs-common-config": "بوتون قابیقلار اوچون پایلاشمیش سی‌اس‌اس/جاوااسکریپت:",
        "prefs-reset-intro": "ترجیحلرینیزی سایتین ایلک فرض ائدیلنلرینه دؤندرمک اوچون، بو صحیفه‌دن ایستیفاده ائده بیلرسینیز.\nبو ایش قایتاریلا بیلمز.",
        "prefs-emailconfirm-label": "ایمیل دوغرولاماسی:",
        "youremail": "ایمیل:",
index a44a7c0..fbfde93 100644 (file)
        "userjspreview": "'''Был бары тик JavaScript файлын алдан ҡарау ғына, ул әле һаҡланмаған!'''",
        "sitecsspreview": "'''Һеҙ CSS файлын алдан ҡарайһығыҙ ғына икәнен иҫегеҙҙә тотоғоҙ.'''\n'''Ул әле һаҡланмаған!'''",
        "sitejspreview": "'''Һеҙ JavaScript кодын алдан ҡарайһығыҙ ғына икәнен иҫегеҙҙә тотоғоҙ.'''\n'''Ул әле һаҡланмаған!'''",
-       "userinvalidcssjstitle": "'''Иғтибар:''' \"$1\" биҙәү темаһы табылманы. Иҫтә тотоғоҙ, .css һәм .js ҡулланыусы биттәренең исемдәре тик бәләкәй хәрефтәрҙән генә торорға тейеш. Мәҫәлән: {{ns:user}}:Foo/vector.css, ә {{ns:user}}:Foo/Vector.css түгел!",
+       "userinvalidconfigtitle": "'''Иғтибар:''' \"$1\" биҙәү темаһы табылманы. Иҫтә тотоғоҙ, .css һәм .js ҡулланыусы биттәренең исемдәре тик бәләкәй хәрефтәрҙән генә торорға тейеш. Мәҫәлән: {{ns:user}}:Foo/vector.css, ә {{ns:user}}:Foo/Vector.css түгел!",
        "updated": "(Яңыртылды)",
        "note": "'''Иҫкәрмә:'''",
        "previewnote": "'''Ҡарап сығыу өлгөһө, әлегә үҙгәрештәр яҙҙырылмаған!'''\nҺеҙҙең үҙгәртеүҙәр әле яҙылмаған!",
        "prefs-files": "Файлдар",
        "prefs-custom-css": "Үҙ CSS",
        "prefs-custom-js": "Үҙ JS",
-       "prefs-common-css-js": "Бөтә күренештәр өсөн дөйөм CSS/JS:",
+       "prefs-common-config": "Бөтә күренештәр өсөн дөйөм CSS/JS:",
        "prefs-reset-intro": "Был битте, көйләүҙәрегеҙҙе ғәҙәттәге көйләүҙәргә ташлатыу өсөн ҡулланып була.\nРаҫлағандан һуң ғәмәлде кире ҡайтарып булмаясаҡ.",
        "prefs-emailconfirm-label": "Электрон почтаны раҫлау:",
        "youremail": "Электрон почта :",
index 22f8bbb..c1b64d7 100644 (file)
        "userjspreview": "''''په یاد دار که شما فقط وتی کاربری  JavaScript بازبینی/آزمایش کنگیت، هنگت ذخیره نه بوتت!''''",
        "sitecsspreview": "<strong> شمارء هیالداری ببیت که انیگء تهنا پیشچارگ چه ائ سی اس اسء رء گند ات.\nآئی انگت ذخیرگ نبیتگ انت </strong>",
        "sitejspreview": "<strong> شمارء هیالداری ببیت که انیگء تهنا پیشچارگ چه ائ جاوا اسکریپٹء رء گند ات.\nآئی انگت ذخیرگ نبیتگ انت </strong>",
-       "userinvalidcssjstitle": "'''هوژاری:''هچ جلدی نیست\"$1\".\nبزان که صفحات .css و .js چه عناوین گون هوردین حرف استفاده کننت، مثلا {{ns:user}}:Foo/vector.css بدل به په {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''هوژاری:''هچ جلدی نیست\"$1\".\nبزان که صفحات .css و .js چه عناوین گون هوردین حرف استفاده کننت، مثلا {{ns:user}}:Foo/vector.css بدل به په {{ns:user}}:Foo/Vector.css.",
        "updated": "(په روچ بیتگین)",
        "note": "'''یادداشت:'''",
        "previewnote": "<strong> بزان که ائ تهنا یک پیشچارگء انت</strong>\nشمئی ٹگلان انگتء ذخیرگ نبیتگ انت!",
        "prefs-files": "فایلان",
        "prefs-custom-css": "رسمی سی‌اس‌اس",
        "prefs-custom-js": "رسمی جی‌اس",
-       "prefs-common-css-js": "یک پیمین سی‌اس‌اس/جاوااسکریپٹ پر پهکین اسکین:",
+       "prefs-common-config": "یک پیمین سی‌اس‌اس/جاوااسکریپٹ پر پهکین اسکین:",
        "prefs-reset-intro": "شما توانت چه ای  صفحه په واترینگ تنظیمات وت په پیش‌فرض استفاده کنیت. ای کار بازگشت‌ناپذیر انت.",
        "prefs-emailconfirm-label": "تایید کتن ایمیل:",
        "youremail": "ایمیل:",
index 2208f5b..f234fe2 100644 (file)
        "userjspreview": "'''Giromdomon tabi na pigtetest/pighihiling mo sana an patanaw kan saimong JavaScript nin paragamit, dai pa ini naitagama!'''",
        "sitecsspreview": "'''Giromdoma baya na ika nagtatanaw pa sana kaining CSS.'''\n'''Ini dae pa tabi naitatagama!'''",
        "sitejspreview": "'''Giromdoma baya na ika nagtatatanaw pa sana kaining koda sa JavaScript.'''\n'''Ini dae pa tabi naitatagama!'''",
-       "userinvalidcssjstitle": "'''Patanid:''' Mayong ''skin'' na \"$1\". Giromdomon tabî na an .css asin .js na mga páhina naggagamit nin titulong nakasurat sa sadit na letras, halimbawa {{ns:user}}:Foo/vector.css bakong {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Patanid:''' Mayong ''skin'' na \"$1\". Giromdomon tabî na an .css asin .js na mga páhina naggagamit nin titulong nakasurat sa sadit na letras, halimbawa {{ns:user}}:Foo/vector.css bakong {{ns:user}}:Foo/Vector.css.",
        "updated": "(Pinagsugpunan na)",
        "note": "'''Paisi:'''",
        "previewnote": "'''Giromdoma na ini sarong patanaw pa sana.'''\nAn saimong mga pinagriliwat dae pa tabi naitatagama!",
        "prefs-files": "Mga dokumento",
        "prefs-custom-css": "Kustombreng CSS",
        "prefs-custom-js": "Kustombreng JavaScript",
-       "prefs-common-css-js": "Pinagheras na CSS/JavaScript para sa gabos na mga kalapatan:",
+       "prefs-common-config": "Pinagheras na CSS/JavaScript para sa gabos na mga kalapatan:",
        "prefs-reset-intro": "Ika makakagamit kaining pahina tanganing ilapat giraray an saimong mga kabotan sa panugmad kan sayt.\nIni dae tabi matitingkog.",
        "prefs-emailconfirm-label": "Kumpirmasyon sa E-koreo",
        "youremail": "E-surat:",
index c428513..0421dbd 100644 (file)
        "userjspreview": "<strong>Памятайце, што гэта толькі папярэдні прагляд Вашага JavaScript. Ён яшчэ не запісаны!</strong>",
        "sitecsspreview": "<strong>Памятайце, што гэта толькі папярэдні прагляд гэтага CSS.\nЁн яшчэ не захаваны!</strong>",
        "sitejspreview": "<strong>Памятайце, што гэта толькі папярэдні прагляд гэтага коду JavaScript.\nЁн яшчэ не захаваны!</strong>",
-       "userinvalidcssjstitle": "<strong>Папярэджаньне:</strong> няма тэмы афармленьня «$1».\nПамятайце, што ўласныя старонкі .css і .js павінны мець назву, якая складаецца з малых літараў, напрыклад, {{ns:user}}:Хтосьці/vector.css, а не {{ns:user}}:Хтосьці/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Папярэджаньне:</strong> няма тэмы афармленьня «$1».\nПамятайце, што ўласныя старонкі .css і .js павінны мець назву, якая складаецца з малых літараў, напрыклад, {{ns:user}}:Хтосьці/vector.css, а не {{ns:user}}:Хтосьці/Vector.css.",
        "updated": "(Абноўлена)",
        "note": "<strong>Заўвага:</strong>",
        "previewnote": "<strong>Гэта толькі папярэдні прагляд.</strong>\nВашыя зьмены яшчэ не былі захаваныя!",
        "prefs-files": "Файлы",
        "prefs-custom-css": "Уласны CSS",
        "prefs-custom-js": "Уласны JavaScript",
-       "prefs-common-css-js": "Агульны CSS/JavaScript для ўсіх тэмаў афармленьня:",
+       "prefs-common-config": "Агульны CSS/JavaScript для ўсіх тэмаў афармленьня:",
        "prefs-reset-intro": "Вы можаце выкарыстаць гэтую старонку для замены вашых наладаў на налады сайту па змоўчаньні.\nГэтае дзеяньне ня можа быць адмененае.",
        "prefs-emailconfirm-label": "Пацьверджаньне адрасу электроннай пошты:",
        "youremail": "Адрас электроннай пошты:",
        "right-viewsuppressed": "праглядаць вэрсіі старонак, схаваныя ад усіх удзельнікаў",
        "right-suppressionlog": "Прагляд прыватных журналаў",
        "right-block": "Блякаваньне іншых удзельнікаў ад рэдагаваньняў",
-       "right-blockemail": "блÑ\8fкаванÑ\8cне Ñ\96нÑ\88Ñ\8bÑ\85 Ñ\9eдзелÑ\8cнÑ\96каÑ\9e Ð°Ð´ Ð´Ð°Ñ\81Ñ\8bлкÑ\96 Ñ\8dлекÑ\82Ñ\80оннай Ð¿Ð¾Ñ\88Ñ\82Ñ\8b",
-       "right-hideuser": "блякаваньне рахунку ўдзельніка і яго хаваньне",
+       "right-blockemail": "Ð\91лÑ\8fкаванÑ\8cне Ñ\9eдзелÑ\8cнÑ\96каÑ\9e Ð°Ð´ Ð°Ð´Ð¿Ñ\80аÑ\9eкÑ\96 Ð»Ñ\96Ñ\81Ñ\82оÑ\9e Ñ\8dлекÑ\82Ñ\80оннай Ð¿Ð¾Ñ\88Ñ\82ай",
+       "right-hideuser": "Ð\91лякаваньне рахунку ўдзельніка і яго хаваньне",
        "right-ipblock-exempt": "абход блякаваньняў IP-адрасоў, аўта-блякаваньняў і блякаваньняў дыяпазонаў",
        "right-unblockself": "разблякаваньне самога сябе",
        "right-protect": "зьмена ўзроўню абароны старонак і рэдагаваньне каскадна абароненых старонак",
index 795ab33..5a6d37c 100644 (file)
        "userjspreview": "<strong>Памятайце, што гэта толькі выпрабаванне/папярэдні паказ вашага JavaScript. Праўкі яшчэ не замацаваныя!</strong>",
        "sitecsspreview": "<strong>Памятайце, што гэта толькі папярэдні паказ вашага CSS.\nПраўкі яшчэ не замацаваныя!</strong>",
        "sitejspreview": "<strong>Памятайце, што гэта толькі папярэдні паказ вашага JavaScript.\nПраўкі яшчэ не замацаваныя!</strong>",
-       "userinvalidcssjstitle": "<strong>Увага:</strong> Няма вокладкі з назвай \"$1\". Памятайце, што свае старонкі .css і .js называюцца толькі малымі літарамі, такім чынам, напр., {{ns:user}}:Foo/vector.css, а не {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Увага:</strong> Няма вокладкі з назвай \"$1\". Памятайце, што свае старонкі .css і .js называюцца толькі малымі літарамі, такім чынам, напр., {{ns:user}}:Foo/vector.css, а не {{ns:user}}:Foo/Vector.css.",
        "updated": "(Абноўлена)",
        "note": "<strong>Заўвага:</strong>",
        "previewnote": "<strong>Памятайце, гэта толькі папярэдні паказ.</strong> Праўкі яшчэ не замацаваныя!",
        "prefs-files": "Файлы",
        "prefs-custom-css": "Уласны CSS",
        "prefs-custom-js": "Уласны JS",
-       "prefs-common-css-js": "Агульны CSS/JavaScript для ўсіх вокладак:",
+       "prefs-common-config": "Агульны CSS/JavaScript для ўсіх вокладак:",
        "prefs-reset-intro": "Тут можна вярнуць свае настройкі да прадвызначэнняў, прынятых на гэтай пляцоўцы.\nАдкаціць гэтае дзеянне нельга.",
        "prefs-emailconfirm-label": "Пацвярджэнне адраса эл.пошты:",
        "youremail": "Эл.пошта *",
index 2b07773..53d0979 100644 (file)
        "userjspreview": "<strong>Не забравяйте, че това е само изпробване/предварителен преглед на кода на JavaScript.\nСтраницата все още не е съхранена!</strong>",
        "sitecsspreview": "<strong>Не забравяйте, че това е само предварителен преглед на този CSS.\nТой все още не е съхранен!</strong>",
        "sitejspreview": "<strong>Не забравяйте, че това е само предварителен преглед на този JavaScript код.\nТой все още не е съхранен!</strong>",
-       "userinvalidcssjstitle": "<strong>Внимание:</strong> Не съществува облик „$1“. Необходимо е да се знае, че имената на потребителските ви страници за CSS и JavaScript трябва да се състоят от малки букви, например: „{{ns:user}}:Иван/vector.css“ (а не „{{ns:user}}:Иван/Vector.css“).",
+       "userinvalidconfigtitle": "<strong>Внимание:</strong> Не съществува облик „$1“. Необходимо е да се знае, че имената на потребителските ви страници за CSS и JavaScript трябва да се състоят от малки букви, например: „{{ns:user}}:Иван/vector.css“ (а не „{{ns:user}}:Иван/Vector.css“).",
        "updated": "(обновена)",
        "note": "<strong>Забележка:</strong>",
        "previewnote": "<strong>Обърнете внимание, че това е само предварителен преглед.</strong>\nПромените все още не са съхранени!",
        "prefs-files": "Файлове",
        "prefs-custom-css": "Личен CSS",
        "prefs-custom-js": "Личен JS",
-       "prefs-common-css-js": "Общи настройки на CSS/JS за всички облици:",
+       "prefs-common-config": "Общи настройки на CSS/JS за всички облици:",
        "prefs-reset-intro": "Тази страница може да се използва за възстановяване на потребителските настройки към стандартните за сайта.\nТова действие е необратимо.",
        "prefs-emailconfirm-label": "Потвърждаване на адрес за е-поща:",
        "youremail": "Е-поща:",
index c25ab3e..9c2cf41 100644 (file)
        "prefs-files": "فایل ئان",
        "prefs-custom-css": "سی‌اس‌اس شخصی",
        "prefs-custom-js": "شخصی ئین جاوااسکریپت",
-       "prefs-common-css-js": "سی‌اس‌اس/جاوااسکریپت مشترک په موچین پوسته‌ئان:",
+       "prefs-common-config": "سی‌اس‌اس/جاوااسکریپت مشترک په موچین پوسته‌ئان:",
        "prefs-emailconfirm-label": "ایمیلئ تائید کورتین:",
        "youremail": "ایمیل:",
        "username": "{{GENDER:$1|کار زوروکئ نام}}:",
index bda0e06..f03f266 100644 (file)
        "userjspreview": "<strong>याद रहे की आप अपनी सदस्य जावास्क्रिप्ट के खाली टेस्ट करत बानी/नमूना देखत बानी।\nई अबहिन सहेजल ना गइल बाटे।</strong>",
        "sitecsspreview": "<strong>याद रहे की आप ए CSS क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
        "sitejspreview": "<strong>याद रहे की आप ए जावास्क्रिप्ट कोड क खाली नमूना देखत बानी।\nई अबहिन ले सहेजल ना गइल बा!</strong>",
-       "userinvalidcssjstitle": "<strong>चेतावनी:</strong> कौनों skin \"$1\"नइखे।\nCustom .css आ .js पन्ना सभ छोटका अक्षर में टाइटिल इस्तेमाल करे लें जइसे की, {{ns:user}}:Foo/vector.css ना की {{ns:user}}:Foo/Vector.css।",
+       "userinvalidconfigtitle": "<strong>चेतावनी:</strong> कौनों skin \"$1\"नइखे।\nCustom .css आ .js पन्ना सभ छोटका अक्षर में टाइटिल इस्तेमाल करे लें जइसे की, {{ns:user}}:Foo/vector.css ना की {{ns:user}}:Foo/Vector.css।",
        "updated": "(अपडेट करल गईल)",
        "note": "'''सूचना:'''",
        "previewnote": "'''याद रखीं, इ एगो झलक मात्र हो।'''\nराउर बदलाव अभी तक सुरक्षित नईखे करल गईल!",
        "prefs-files": "फाइल सब",
        "prefs-custom-css": "व्यक्तिगत CSS",
        "prefs-custom-js": "व्यक्तिगत जावास्क्रिप्ट",
-       "prefs-common-css-js": "सगरी जिल्द खातिर साझा CSS/जावास्क्रिप्ट:",
+       "prefs-common-config": "सगरी जिल्द खातिर साझा CSS/जावास्क्रिप्ट:",
        "prefs-reset-intro": "रउआँ आपन पसंद बदल के डिफाल्ट करे खातिर ए पन्ना के इस्तेमाल नइखीं कर सकत।\n\nई फिर से वापस ना हो पाई।",
        "prefs-emailconfirm-label": "ईमेल जाँच:",
        "youremail": "ईमेल:",
index e1a1d8e..264f129 100644 (file)
        "userjspreview": "'''Ingatakan bahwasa Pian tis/manilik pamakai JavaScript Pian.'''\n'''Nangini baluman tasimpan pulang!'''",
        "sitecsspreview": "'''Ingatakan bahwasa Pian manilik CSS ini haja.'''\n'''Nangini lagi baluman tasimpan!'''",
        "sitejspreview": "'''Ingatakan bahwasa Pian manilik JavaScript code ini haja.'''\n'''Nangini lagi baluman tasimpan!'''",
-       "userinvalidcssjstitle": "'''Paringatan:''' Kadada kulit \"$1\".\nInatakan bahwasa saragam  tungkaran-tungkaran .css wan .js mamuruk aksara halus, cuntuh {{ns:user}}:Foo/vector.css sawagai tandingan {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Paringatan:''' Kadada kulit \"$1\".\nInatakan bahwasa saragam  tungkaran-tungkaran .css wan .js mamuruk aksara halus, cuntuh {{ns:user}}:Foo/vector.css sawagai tandingan {{ns:user}}:Foo/Vector.css.",
        "updated": "(Dihanyarakan)",
        "note": "'''Catatan:'''",
        "previewnote": "'''Ingatakanlah bahwasa ngini titilikan haja''' Paubahan Pian baluman disimpan!",
        "prefs-files": "Barakas",
        "prefs-custom-css": "Saragamakan CSS",
        "prefs-custom-js": "Saraganakan JavaScript",
-       "prefs-common-css-js": "Babagi CSS/JavaScript gasan samunyaan skin:",
+       "prefs-common-config": "Babagi CSS/JavaScript gasan samunyaan skin:",
        "prefs-reset-intro": "Pian kawa mamuruk tungkaran ini hagan setel bulik kakatujuan Pian ka default situs.\nIni kada kawa diwalangi.",
        "prefs-emailconfirm-label": "Payakinakan suril:",
        "youremail": "Suril:",
index e39abd1..003966a 100644 (file)
        "userjspreview": "'''মনে রাখুন আপনি আপনার ব্যবহারকারী জাভাস্ক্রিপ্ট কেবলমাত্র পরীক্ষা/প্রাকদর্শন করছেন। এটা এখনও সংরক্ষণ করা হয়নি!'''",
        "sitecsspreview": "'''মনে রাখবেন আপনি আপনার জন্য বরাদ্ধকৃত সিএসএস প্রাকদর্শন করছেন।\nএটা এখনও সংরক্ষণ করা হয়নি!'''",
        "sitejspreview": "'''মনে রাখুন আপনি আপনার ব্যবহারকারী জাভাস্ক্রিপ্ট কেবলমাত্র প্রাকদর্শন করছেন।'''\n'''এটা এখনও সংরক্ষণ করা হয়নি!'''",
-       "userinvalidcssjstitle": "'''সতর্কীকরণ:''' \"$1\" নামে কোন আবরণ নেই। মনে রাখবেন, পছন্দমাফিক .css এবং .js পাতাগুলি ছোট হাতের শিরোনাম ব্যবহার করে, যেমন {{ns:user}}:Foo/vector.css; কিন্তু এরকম শিরোনাম নয়: {{ns:user}}:Foo/Vector.css",
+       "userinvalidconfigtitle": "'''সতর্কীকরণ:''' \"$1\" নামে কোন আবরণ নেই। মনে রাখবেন, পছন্দমাফিক .css এবং .js পাতাগুলি ছোট হাতের শিরোনাম ব্যবহার করে, যেমন {{ns:user}}:Foo/vector.css; কিন্তু এরকম শিরোনাম নয়: {{ns:user}}:Foo/Vector.css",
        "updated": "(হালনাগাদ)",
        "note": "<strong>টীকা:</strong>",
        "previewnote": "'''খেয়াল করুন, এটি একটি প্রাকদর্শন মাত্র।'''\nআপনার পরিবর্তন এখনও সংরক্ষণ করা হয়নি!",
        "prefs-files": "ফাইল",
        "prefs-custom-css": "স্বনির্ধারিত CSS",
        "prefs-custom-js": "স্বনির্ধারিত JS",
-       "prefs-common-css-js": "সকল ক্ষেত্রের জন্য সিএসএস/জাভাস্ক্রিপ্ট",
+       "prefs-common-config": "সকল ক্ষেত্রের জন্য সিএসএস/জাভাস্ক্রিপ্ট",
        "prefs-reset-intro": "আপনি এই পাতা ব্যবহার করে আপনার পছন্দসমূহকে সাইটের পূর্বপ্রদত্ত সেটিংসে পরিবর্তন করতে পারেন।\nপরিবর্তন করার পর এটা  আর ফিরিয়ে আনা যাবে না।",
        "prefs-emailconfirm-label": "ই-মেইল নিশ্চিতকরণ:",
        "youremail": "ইমেইল *",
index bee4f0d..476c3a2 100644 (file)
        "userjsyoucanpreview": "'''টিপ:''' 'আগচা' গুথামগত যাতিয়া তর জাভাস্ক্রিপ্ট অতা চুমিসেতানা কিতা হবা করে চা।",
        "usercsspreview": "'''তি মনে থইস এহান তর সিএসএসর আগচাহান।'''\n'''এহান এপাগাউ ইতু নাইসে!'''",
        "userjspreview": "'''তি মনে থইস এহান তর জাভাস্ক্রিপ্টর পরীক্ষা/আগচাহান।'''\n'''এহান এপাগাউ ইতু নাইসে!'''",
-       "userinvalidcssjstitle": "'''সিঙুইস:''' \"$1\" নাঙর কোন সর নেই।\nমনে থইস .css বারো .js পাতার নাঙ এতা রূহিবৃত্তির মাতুঙে হুরকা আতর ইকার মেয়েকল অরতাহে, যেসাদে {{ns:user}}:Foo/vector.css; কিন্তু এসাদে চিঙনাঙ নাইব: {{ns:user}}:Foo/Vector.css",
+       "userinvalidconfigtitle": "'''সিঙুইস:''' \"$1\" নাঙর কোন সর নেই।\nমনে থইস .css বারো .js পাতার নাঙ এতা রূহিবৃত্তির মাতুঙে হুরকা আতর ইকার মেয়েকল অরতাহে, যেসাদে {{ns:user}}:Foo/vector.css; কিন্তু এসাদে চিঙনাঙ নাইব: {{ns:user}}:Foo/Vector.css",
        "updated": "(আপডেট)",
        "note": "'''নোট:'''",
        "previewnote": "'''খিয়াল কর, এহান হুদ্দা আগচাহান।'''\nফারাকহান এপাগাউ ইতু করানি নাইসে!",
        "prefs-files": "ফাইল",
        "prefs-custom-css": "স্বনির্ধারিত CSS",
        "prefs-custom-js": "স্বনির্ধারিত JS",
-       "prefs-common-css-js": "হাব্বি স্কিনর কা শেয়ারড CSS/JavaScript:",
+       "prefs-common-config": "হাব্বি স্কিনর কা শেয়ারড CSS/JavaScript:",
        "youremail": "ই-মেইল *:",
        "yourrealname": "আৱৈপা নাংহান *:",
        "yourlanguage": "ঠারহান:",
index 8f05d05..70594d3 100644 (file)
        "userjspreview": "'''Dalc'hit soñj emaoc'h o rakwelet pe o testiñ ho kod javascript deoc'h ha n'eo ket bet enrollet c'hoazh!'''",
        "sitecsspreview": "'''Dalc'hit soñj n'emaoc'h ken nemet o rakwelet ar follenn CSS-mañ.'''\n'''N'eo ket bet enrollet evit c'hoazh!'''",
        "sitejspreview": "'''Dalc'hit soñj n'emaoc'h ken nemet o rakwelet ar c'hod JavaScript-mañ.'''\n'''N'eo ket bet enrollet evit c'hoazh!'''",
-       "userinvalidcssjstitle": "'''Diwallit:''' N'eus tamm gwiskadur \"$1\" ebet. Ho pez soñj e vez implijet lizherennoù bihan goude an anv implijer hag ar veskell / gant ar pajennoù personel dezho un astenn .css ha .js; da skouer eo mat ar follenn stil {{ns:user}}:Foo/vector.css ha faziek an hini {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Diwallit:''' N'eus tamm gwiskadur \"$1\" ebet. Ho pez soñj e vez implijet lizherennoù bihan goude an anv implijer hag ar veskell / gant ar pajennoù personel dezho un astenn .css ha .js; da skouer eo mat ar follenn stil {{ns:user}}:Foo/vector.css ha faziek an hini {{ns:user}}:Foo/Vector.css.",
        "updated": "(Hizivaet)",
        "note": "'''Notenn :'''",
        "previewnote": "'''Diwallit mat, n'eus ken ur rakweled eus an destenn-mañ.'''\nN'eo ket bet enrollet ho kemmoù evit c'hoazh !",
        "prefs-files": "Restroù",
        "prefs-custom-css": "CSS personelaet",
        "prefs-custom-js": "JS personelaet",
-       "prefs-common-css-js": "JavaScript ha CSS kenrannet evit an holl wiskadurioù :",
+       "prefs-common-config": "JavaScript ha CSS kenrannet evit an holl wiskadurioù :",
        "prefs-reset-intro": "Ober gant ar bajenn-mañ a c'hallit evit adlakaat ho penndibaboù dre ziouer evit al lec'hienn-mañ. Kement-se n'hallo ket bezañ disc'hraet da c'houde.",
        "prefs-emailconfirm-label": "Kadarnaat ar postel :",
        "youremail": "Postel :",
index 991dc1e..a015b14 100644 (file)
        "userjspreview": "<strong>Zapamtite da je ovo samo pregled Vašeg JavaScripta.\nStranica još nije sačuvana!</strong>",
        "sitecsspreview": "'''Zapamtite ovo je samo izgled ovog CSS-a.'''\n'''Još uvijek nije sačuvan!'''",
        "sitejspreview": "'''Zapamtite ovo je samo izgled ovog koda JavaScripte.'''\n'''Još uvijek nije sačuvan!'''",
-       "userinvalidcssjstitle": "<strong>Upozorenje:</strong> Ne postoji tema \"$1\".\nNe zaboravite da imena stranica s .css i .js kodom počinju malim slovom, npr, {{ns:user}}:Foo/vector.css, a ne {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Upozorenje:</strong> Ne postoji tema \"$1\".\nNe zaboravite da imena stranica s .css i .js kodom počinju malim slovom, npr, {{ns:user}}:Foo/vector.css, a ne {{ns:user}}:Foo/Vector.css.",
        "updated": "(Osvježeno)",
        "note": "'''Pažnja:'''",
        "previewnote": "<strong>Ne zaboravite da je ovo samo pregled.</strong>\nVaše izmjene još nisu sačuvane!",
        "prefs-files": "Datoteke",
        "prefs-custom-css": "Prilagođeni CSS",
        "prefs-custom-js": "Prilagođeni JavaScript",
-       "prefs-common-css-js": "Dijeljeni CSS/JavaScript za sve teme:",
+       "prefs-common-config": "Dijeljeni CSS/JavaScript za sve teme:",
        "prefs-reset-intro": "Možete koristiti ovu stranicu da poništite Vaše postavke na ovom sajtu na pretpostavljene vrijednosti.\nOvo se ne može vratiti unazad.",
        "prefs-emailconfirm-label": "Potvrda e-pošte:",
        "youremail": "Adresa e-pošte:",
index 13918d8..4fe01e6 100644 (file)
        "userjspreview": "'''Recordeu que només estau provant/previsualitzant el vostre JavaScript, encara no ho heu desat!'''",
        "sitecsspreview": "'''Adoneu-vos que esteu veient una vista prèvia d'aquest full d'estil CSS.'''\n'''Encara no s'ha desat!'''",
        "sitejspreview": "'''Tingueu present que esteu previsualitzant aquest codi Javascript.'''\n'''Encara no s'ha desat!'''",
-       "userinvalidcssjstitle": "'''Atenció:''' No existeix l'aparença «$1». Recordeu que les subpàgines personalitzades amb extensions .css i .js utilitzen el títol en minúscules, per exemple, {{ns:user}}:NOM/vector.css no és el mateix que {{ns:user}}:NOM/Vector.css.",
+       "userinvalidconfigtitle": "'''Atenció:''' No existeix l'aparença «$1». Recordeu que les subpàgines personalitzades amb extensions .css i .js utilitzen el títol en minúscules, per exemple, {{ns:user}}:NOM/vector.css no és el mateix que {{ns:user}}:NOM/Vector.css.",
        "updated": "(Actualitzat)",
        "note": "'''Nota:'''",
        "previewnote": "<strong>Recordeu que això és només una previsualització.</strong>\nEls vostres canvis encara no s’han desat!",
        "prefs-files": "Fitxers",
        "prefs-custom-css": "CSS personalitzat",
        "prefs-custom-js": "JS personalitzat",
-       "prefs-common-css-js": "CSS/JS compartit per tots els skins:",
+       "prefs-common-config": "CSS/JS compartit per tots els skins:",
        "prefs-reset-intro": "Podeu usar aquesta pàgina per a restablir les vostres preferències als valors per defecte.\nNo es podrà desfer el canvi.",
        "prefs-emailconfirm-label": "Confirmació de correu electrònic:",
        "youremail": "Correu electrònic:",
index 66aa5b2..0389de4 100644 (file)
        "prefs-files": "Файлаш",
        "prefs-custom-css": "Долахь йолу CSS",
        "prefs-custom-js": "Долахь йолу JS",
-       "prefs-common-css-js": "Юкъара CSS/JS массо кеч даран темийн:",
+       "prefs-common-config": "Юкъара CSS/JS массо кечдаран темийн:",
        "prefs-reset-intro": "ХӀара агӀо лело мега ахьа нисбина гӀирс Ӏадбитаран кепаца юха бокхуш.\nХӀара дешдерг кхочушъ динчул  тӀехьа хьан йиш хир-яц и юха меттахӀотто.",
        "prefs-emailconfirm-label": "Электронан пошт бакъ яр:",
        "youremail": "Электронан пошт:",
        "uploadnewversion-linktext": "Чуяккха керла верси хӀокху файлан",
        "shared-repo-from": "$1 чура",
        "shared-repo": "юкъара Ӏалаше меттиг",
-       "shared-repo-name-wikimediacommons": "Ð\92икигÑ\83лам",
+       "shared-repo-name-wikimediacommons": "Ð\92икилаÑ\80ма",
        "upload-disallowed-here": "Хьан бакъо яц хӀара файл юху дӀаяздан.",
        "filerevert": "Тохарлера верси юхаерзор $1",
        "filerevert-legend": "Файлан верси юхаерзо",
        "move-leave-redirect": "Ӏадйита дӀасахьажорг",
        "protectedpagemovewarning": "'''ДӀахьедар.''' ХӀара агӀо ларйина ю; цӀе хийца я нисъян а бакъо йолуш куьйгалхой бен бац.\nЛахахь тептаро балийна тӀаьхьаралера дӀаязбина хаам:",
        "semiprotectedpagemovewarning": "'''ДӀахьедо.''' ХӀара агӀо ларйина ю; дӀабазбиначу декъашхошка бе цӀе хийцалуш яц.\nЛахахьа тептаро балийна тӀаьххьаралера дӀаязбина хаам:",
-       "move-over-sharedrepo": "== Файл йолуш ю ==\nВикигулам чохь йолуш ю [[:$1]]. ХӀокху файлан цӀе хийцича Викигулам чуьраниг дӀакъовлу.",
+       "move-over-sharedrepo": "Викиларма чохь йолуш ю [[:$1]]. ХӀокху файлан цӀе хийцича Викиларма чуьраниг дӀакъовлу.",
        "export": "АгӀонаш араяхар",
        "exporttext": "Шуьга далур ду кхечу меттера чудахарш, йоза а хийцаме тептарш билгалла йолу агӀонаш я гулдина йолу агӀонаш хӀокху XML барамца, юха тӀаьхьа чура [[Special:Import|хьаэцалурдолш]] кхечу вики-хьалхен, болх беш йолу хӀокху MediaWiki гӀирсаца.\n\nКхечу меттера яззамаш чуяха, чу язъе цӀе тадечу метте, цхьа могӀан цӀе могӀаршкахь, юха харжа лаьий шуна кхечу меттигера чуяха массо яззамашна истори хийцамбарш я тӀаьххьарлера яззаман верси.\n\nШуьга кхи далундерг, лелаеш йолу адресан хьажорг кхечу меттера чудаха тӀаьххьарлерачу версин яззамаш. Масала оцу яззаман [[{{MediaWiki:Mainpage}}]] хӀара хира ю хьажорг [[{{#Special:Export}}/{{MediaWiki:Mainpage}}]].",
        "exportall": "Массо агӀонаш экспорт ян",
index 76d1f58..63358cf 100644 (file)
        "userjspreview": "'''لەیادت بێ کە ئێستە تەنها پێشبینین\\تاقی‌کردنەوەی جاڤاسکریپتی بەکارهێنەریەکەت دەکەی.'''\n'''هێشتا پاشەکەوت نەبووه !'''",
        "sitecsspreview": "<strong>لە بیرت ببێت کە تەنھا خەریکی پێشبینینی ئەم CSSـە دەبینیت.\nھێشتا پاشەکەوەت نەکراوە!</strong>",
        "sitejspreview": "'''لە بیرت نەچێت ئەمە تەنیا پێشبینینی ئەم کۆدەی جاڤاسکریپتە.'''\n'''گۆڕانکارییەکانت ھێشتا پاشەکەوت نەکراون!'''",
-       "userinvalidcssjstitle": "'''ئاگادارکردنەوە:''' پێست نیە بۆ \"$1\".\nلەیادت بێ کە لاپەڕەکانی‌ .css و .js لە بابەت بە پیتی بچووک کەڵک وەر ئەگرن. وەک {{ns:user}}:Foo/vector.css نە وەک {{ns:user}}:Foo/Vector.css .",
+       "userinvalidconfigtitle": "'''ئاگادارکردنەوە:''' پێست نیە بۆ \"$1\".\nلەیادت بێ کە لاپەڕەکانی‌ .css و .js لە بابەت بە پیتی بچووک کەڵک وەر ئەگرن. وەک {{ns:user}}:Foo/vector.css نە وەک {{ns:user}}:Foo/Vector.css .",
        "updated": "(نوێ‌کراوە)",
        "note": "'''تێبینی:'''",
        "previewnote": "'''لە بیرت نەچێت ئەمە تەنیا پێشبینینە.'''\nگۆڕانکارییەکانت ھێشتا پاشەکەوت نەکراون!",
        "prefs-files": "پەڕگەکان",
        "prefs-custom-css": "CSSی دڵخواز",
        "prefs-custom-js": "جاڤاسکریپتی دڵخواز",
-       "prefs-common-css-js": "سی‌ئێس‌ئێس/جاڤاسکریپتی ھاوبەش بۆ گشت پێستەکان:",
+       "prefs-common-config": "سی‌ئێس‌ئێس/جاڤاسکریپتی ھاوبەش بۆ گشت پێستەکان:",
        "prefs-reset-intro": "دەتوانی لەم لاپەڕە بۆ گەڕانەوەی هەڵبژاردەکانت بۆ بنچینەیی ماڵپەر کەڵک وەرگریت.\nگەر ئەوە بکەی ئیتر گۆڕانەکەت ناگەڕێتەوە.",
        "prefs-emailconfirm-label": "پشتڕاستکردنەوەی ئیمەیل:",
        "youremail": "ئیمەیل:",
index 3eaeffe..df4f8fd 100644 (file)
        "userjsyoucanpreview": "'''Тевсие:''' Янъы JavaScript-инъизни тешкермек ичюн саифени сакъламаздан эвель «{{int:showpreview}}» дёгмесине басынъыз.",
        "usercsspreview": "'''Унутманъыз, бу тек бакъып чыкъув - къулланыджы CSS файлынъыз аля даа сакъланмады!'''",
        "userjspreview": "'''Унутманъыз, сиз шимди тек тест этесинъиз я да бакъып чыкъув коресинъиз - къулланыджы JavaScript'и шимдилик сакъланмады.'''",
-       "userinvalidcssjstitle": "'''Ихтар:''' \"$1\" адынен бир тема ёкътыр. тема-ады.css ве .js файлларынынъ адлары кичик афир иле язмакъ керек, яни {{ns:user}}:Темель/'''V'''ector.css дегиль, {{ns:user}}:Темель/'''v'''ector.css.",
+       "userinvalidconfigtitle": "'''Ихтар:''' \"$1\" адынен бир тема ёкътыр. тема-ады.css ве .js файлларынынъ адлары кичик афир иле язмакъ керек, яни {{ns:user}}:Темель/'''V'''ector.css дегиль, {{ns:user}}:Темель/'''v'''ector.css.",
        "updated": "(Янъарды)",
        "note": "'''Ихтар:'''",
        "previewnote": "'''Бу тек бакъып чыкъув, метин аля даа сакъланмагъан!'''",
index 13d8a41..7db085b 100644 (file)
        "userjsyoucanpreview": "'''Tevsiye:''' Yañı JavaScript-iñizni teşkermek içün saifeni saqlamazdan evel \"{{int:showpreview}}\" dögmesine basıñız.",
        "usercsspreview": "'''Unutmañız, bu tek baqıp çıquv - qullanıcı CSS faylıñız alâ daa saqlanmadı!'''",
        "userjspreview": "'''Unutmañız, siz şimdi tek test etesiñiz ya da baqıp çıquv köresiñiz - qullanıcı JavaScript'i şimdilik saqlanmadı.'''",
-       "userinvalidcssjstitle": "'''İhtar:''' \"$1\" adınen bir tema yoqtır. tema-adı.css ve .js fayllarınıñ adları kiçik afir ile yazmaq kerek, yani {{ns:user}}:Temel/'''V'''ector.css degil, {{ns:user}}:Temel/'''v'''ector.css.",
+       "userinvalidconfigtitle": "'''İhtar:''' \"$1\" adınen bir tema yoqtır. tema-adı.css ve .js fayllarınıñ adları kiçik afir ile yazmaq kerek, yani {{ns:user}}:Temel/'''V'''ector.css degil, {{ns:user}}:Temel/'''v'''ector.css.",
        "updated": "(Yañardı)",
        "note": "'''İhtar:'''",
        "previewnote": "'''Bu tek baqıp çıquv, metin alâ daa saqlanmağan!'''",
index 55083ff..9a7f72f 100644 (file)
        "userjspreview": "<strong>Pamatujte, že testujete a prohlížíte pouze náhled svého uživatelského JavaScriptu, jelikož dosud nebyl uložen!</strong>",
        "sitecsspreview": "<strong>Pamatujte, že si prohlížíte jen náhled tohoto CSS, jelikož dosud nebylo uloženo!</strong>",
        "sitejspreview": "<strong>Pamatujte, že testujete a prohlížíte pouze náhled tohoto JavaScriptu, jelikož dosud nebyl uložen!</strong>",
-       "userinvalidcssjstitle": "<strong>Varování:</strong> Vzhled „$1“ neexistuje. Nezapomeňte, že uživatelské .css a .js soubory používají malá písmena, např. {{ns:user}}:{{BASEPAGENAME}}/vector.css, nikoli {{ns:user}}:{{BASEPAGENAME}}/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Varování:</strong> Vzhled „$1“ neexistuje. Nezapomeňte, že uživatelské .css a .js soubory používají malá písmena, např. {{ns:user}}:{{BASEPAGENAME}}/vector.css, nikoli {{ns:user}}:{{BASEPAGENAME}}/Vector.css.",
        "updated": "(Změna uložena)",
        "note": "<strong>Poznámka:</strong>",
        "previewnote": "<strong>Pamatujte, že toto je pouze náhled.</strong>\nZměny dosud nebyly uloženy!",
        "prefs-files": "Soubory",
        "prefs-custom-css": "Uživatelské CSS",
        "prefs-custom-js": "Uživatelský JavaScript",
-       "prefs-common-css-js": "Sdílené CSS/JavaScript pro všechny styly:",
+       "prefs-common-config": "Sdílené CSS/JavaScript pro všechny styly:",
        "prefs-reset-intro": "Pomocí této stránky můžete všechna nastavení vrátit na implicitní hodnoty.\nTuto operaci nelze vrátit zpět.",
        "prefs-emailconfirm-label": "Ověření e-mailu:",
        "youremail": "E-mail:",
index 38984f9..1131d85 100644 (file)
        "prefs-files": "Lopczi",
        "prefs-custom-css": "swój CSS",
        "prefs-custom-js": "swój JavaScript",
-       "prefs-common-css-js": "Wespólny CSS/JS dlô wszëtczich skórków:",
+       "prefs-common-config": "Wespólny CSS/JS dlô wszëtczich skórków:",
        "prefs-reset-intro": "Na ti starnie mòże doprowôdzëc nazôd domëslné nastôwë dlô ti starnë.\nNegò dzéjaniô ni mòżé pòzdze ju copnąc.",
        "prefs-emailconfirm-label": "Pòcwierdzenié e-mailowi adresë:",
        "youremail": "E-mail:",
index 10b4025..b3dcb12 100644 (file)
        "userjspreview": "'''Cofiwch -- dim ond rhagolwg o'ch JavaScript yw hwn; nid yw wedi'i gadw eto!'''",
        "sitecsspreview": "'''Cofiwch - dim ond rhagolwg o'ch CSS yw hwn.'''\n'''Nid yw wedi'i gadw eto!'''",
        "sitejspreview": "'''Cofiwch - dim ond rhagolwg o'ch côd JavaScript yw hwn.'''\n'''Nid yw wedi'i rhoi ar gadw eto!'''",
-       "userinvalidcssjstitle": "'''Rhybudd:''' Nid oes gwedd o'r enw \"$1\".\nCofiwch bod y tudalennau .css a .js yn defnyddio llythrennau bach, e.e. {{ns:user}}:Foo/vector.css yn hytrach na {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Rhybudd:''' Nid oes gwedd o'r enw \"$1\".\nCofiwch bod y tudalennau .css a .js yn defnyddio llythrennau bach, e.e. {{ns:user}}:Foo/vector.css yn hytrach na {{ns:user}}:Foo/Vector.css.",
        "updated": "(Diweddariad)",
        "note": "'''Dalier sylw:'''",
        "previewnote": "'''Cofiwch taw rhagolwg yw hwn.''' Nid yw eich gwaith wedi ei roi ar gadw eto!",
        "prefs-files": "Ffeiliau",
        "prefs-custom-css": "CSS o hunan-ddewis",
        "prefs-custom-js": "JS o hunan-ddewis",
-       "prefs-common-css-js": "CSS/JS ar y cyd ar gyfer pob gwedd:",
+       "prefs-common-config": "CSS/JS ar y cyd ar gyfer pob gwedd:",
        "prefs-reset-intro": "Gallwch ddefnyddio'r dudalen hon i ailosod eich dewisiadau i'r rhai diofyn.\nNi allwch ddadwneud y weithred hon.",
        "prefs-emailconfirm-label": "Cadarnhau'r e-bost:",
        "youremail": "Eich cyfeiriad e-bost",
index e065bb9..f30cdab 100644 (file)
        "userjspreview": "'''Husk at du kun tester/forhåndsviser dit eget javascript, det er ikke gemt endnu!'''",
        "sitecsspreview": "'''Husk, at dette kun er en forhåndsvisning af denne CSS.'''\n'''Det er endnu ikke gemt!'''",
        "sitejspreview": "'''Husk, at du kun ser en forhåndsvisning af denne JavaScriptkode.'''\n'''Det er endnu ikke gemt!'''",
-       "userinvalidcssjstitle": "'''Advarsel:''' Der findes intet skin „$1“. Tænk på, at brugerspecifikke .css- og .js-sider begynder med små bogstaver, altså f.eks. ''{{ns:user}}:Hansen/vector.css'' og ikke ''{{ns:user}}:Hansen/Vector.css''.",
+       "userinvalidconfigtitle": "'''Advarsel:''' Der findes intet skin „$1“. Tænk på, at brugerspecifikke .css- og .js-sider begynder med små bogstaver, altså f.eks. ''{{ns:user}}:Hansen/vector.css'' og ikke ''{{ns:user}}:Hansen/Vector.css''.",
        "updated": "(Opdateret)",
        "note": "'''Bemærk:'''",
        "previewnote": "'''Husk at dette er kun en forhåndsvisning.'''\nDine ændringer er endnu ikke blevet gemt!",
        "prefs-files": "Filer",
        "prefs-custom-css": "Personlig CSS",
        "prefs-custom-js": "Personlig JavaScript",
-       "prefs-common-css-js": "Fælles CSS/JS for alle udseender:",
+       "prefs-common-config": "Fælles CSS/JS for alle udseender:",
        "prefs-reset-intro": "Du kan bruge denne side til at tilbagestille alle dine indstillinger til standardindstillingerne.\nDet kan ikke gøres om.",
        "prefs-emailconfirm-label": "Bekræftelse af e-mail:",
        "youremail": "Din e-mailadresse:",
index 642130b..98438c2 100644 (file)
        "clearyourcache": "'''Hinweis:''' Leere nach dem Speichern den Browser-Cache, um die Änderungen sehen zu können.\n* '''Firefox/Safari:''' ''Umschalttaste'' drücken und gleichzeitig ''Aktualisieren'' anklicken oder entweder ''Ctrl+F5'' oder ''Ctrl+R'' (''⌘+R'' auf dem Mac) drücken\n* '''Google Chrome:''' ''Umschalttaste+Ctrl+R'' (''⌘+Umschalttaste+R'' auf dem Mac) drücken\n* '''Internet Explorer:''' ''Ctrl+F5'' drücken oder ''Ctrl'' drücken und gleichzeitig ''Aktualisieren'' anklicken\n* '''Opera:''' ''Extras → Internetspuren löschen … → Individuelle Auswahl → Den kompletten Cache löschen''",
        "usercssyoucanpreview": "'''Tipp:''' Benutze den «{{int:showpreview}}»-Button, um dein neues CSS vor dem Speichern zu testen.",
        "userjsyoucanpreview": "'''Tipp:''' Benutze den «{{int:showpreview}}»-Button, um dein neues JavaScript vor dem Speichern zu testen.",
-       "userinvalidcssjstitle": "'''Achtung:''' Die Benutzeroberfläche «$1» existiert nicht. Bedenke, dass benutzerspezifische .css- und .js-Seiten mit einem Kleinbuchstaben anfangen müssen, also beispielsweise ''{{ns:user}}:Mustermann/vector.css'' an Stelle von ''{{ns:user}}:Mustermann/Vector.css''.",
+       "userinvalidconfigtitle": "'''Achtung:''' Die Benutzeroberfläche «$1» existiert nicht. Bedenke, dass benutzerspezifische .css- und .js-Seiten mit einem Kleinbuchstaben anfangen müssen, also beispielsweise ''{{ns:user}}:Mustermann/vector.css'' an Stelle von ''{{ns:user}}:Mustermann/Vector.css''.",
        "editing": "Bearbeiten von «$1»",
        "creating": "Erstellen von «$1»",
        "editingsection": "Bearbeiten von «$1» (Abschnitt)",
index dfe7008..f7796da 100644 (file)
        "userjspreview": "'''Beachten Sie, dass Sie nur eine Vorschau Ihres Benutzer-JavaScript betrachten.'''\n'''Es wurde noch nicht gespeichert!'''",
        "sitecsspreview": "'''Beachten Sie, dass Sie nur eine Vorschau dieses CSS betrachten.'''\n'''Es wurde noch nicht gespeichert!'''",
        "sitejspreview": "'''Beachten Sie, dass Sie nur eine Vorschau dieses JavaScript betrachten.'''\n'''Es wurde noch nicht gespeichert!'''",
-       "userinvalidcssjstitle": "'''Achtung:''' Die Benutzeroberfläche „$1“ existiert nicht. Bedenken Sie, dass benutzerspezifische .css- und .js-Seiten mit einem Kleinbuchstaben anfangen müssen, also beispielsweise ''{{ns:user}}:Mustermann/vector.css'' an Stelle von ''{{ns:user}}:Mustermann/Vector.css''.",
+       "userinvalidconfigtitle": "'''Achtung:''' Die Benutzeroberfläche „$1“ existiert nicht. Bedenken Sie, dass benutzerspezifische .css- und .js-Seiten mit einem Kleinbuchstaben anfangen müssen, also beispielsweise ''{{ns:user}}:Mustermann/vector.css'' an Stelle von ''{{ns:user}}:Mustermann/Vector.css''.",
        "previewconflict": "Diese Vorschau gibt den Inhalt des oberen Textfeldes wieder. So wird die Seite aussehen, wenn Sie jetzt speichern.",
        "session_fail_preview": "'''Ihre Bearbeitung konnte nicht gespeichert werden, da Sitzungsdaten verloren gegangen sind.\nBitte versuchen Sie es erneut, indem Sie unter der folgenden Textvorschau nochmals auf „Seite speichern“ klicken.\nSollte das Problem bestehen bleiben, [[Special:UserLogout|melden Sie sich ab]] und danach wieder an.'''",
        "session_fail_preview_html": "'''Ihre Bearbeitung konnte nicht gespeichert werden, da Sitzungsdaten verloren gegangen sind.'''\n\n''Da in {{SITENAME}} das Speichern von reinem HTML aktiviert ist, wurde die Vorschau ausgeblendet, um JavaScript-Attacken vorzubeugen.''\n\n'''Bitte versuchen Sie es erneut, indem Sie unter der folgenden Textvorschau nochmals auf „Seite speichern“ klicken.\nSollte das Problem bestehen bleiben, [[Special:UserLogout|melden Sie sich ab]] und danach wieder an.'''",
index b343b68..2f380a2 100644 (file)
        "userjspreview": "'''Beachte, dass du nur eine Vorschau deines Benutzer-JavaScripts betrachtest.'''\n'''Es wurde noch nicht gespeichert!'''",
        "sitecsspreview": "'''Beachte, dass du nur eine Vorschau dieses CSS betrachtest.'''\n'''Es wurde noch nicht gespeichert!'''",
        "sitejspreview": "'''Beachte, dass du nur eine Vorschau dieses JavaScript betrachtest.'''\n'''Es wurde noch nicht gespeichert!'''",
-       "userinvalidcssjstitle": "'''Achtung:''' Die Benutzeroberfläche „$1“ existiert nicht. Bedenke, dass benutzerspezifische .css- und .js-Seiten mit einem Kleinbuchstaben anfangen müssen, also beispielsweise ''{{ns:user}}:Mustermann/vector.css'' an Stelle von ''{{ns:user}}:Mustermann/Vector.css''.",
+       "userinvalidconfigtitle": "'''Achtung:''' Die Benutzeroberfläche „$1“ existiert nicht. Bedenke, dass benutzerspezifische .css- und .js-Seiten mit einem Kleinbuchstaben anfangen müssen, also beispielsweise ''{{ns:user}}:Mustermann/vector.css'' an Stelle von ''{{ns:user}}:Mustermann/Vector.css''.",
        "updated": "(Geändert)",
        "note": "'''Hinweis:'''",
        "previewnote": "'''Dies ist nur eine Vorschau.'''\nDie Seite wurde noch nicht gespeichert!",
        "prefs-files": "Dateien",
        "prefs-custom-css": "Benutzerdefiniertes CSS",
        "prefs-custom-js": "Benutzerdefiniertes JavaScript",
-       "prefs-common-css-js": "Gemeinsames CSS/JavaScript aller Benutzeroberflächen:",
+       "prefs-common-config": "Gemeinsames CSS/JavaScript aller Benutzeroberflächen:",
        "prefs-reset-intro": "Du kannst diese Seite verwenden, um die Einstellungen auf die Standards zurückzusetzen.\nDies kann nicht mehr rückgängig gemacht werden.",
        "prefs-emailconfirm-label": "Bestätigung:",
        "youremail": "E-Mail-Adresse:",
index de56748..131d189 100644 (file)
        "userjspreview": "'''şıma tena test keni ya ziverqayn seyr keni - karberê JavaScript'i hema qayd nebiyo.'''",
        "sitecsspreview": "'''Şımayê enewke tenya verqaytê dosya da CSS vınenê.''' \n'''Hewna qayd nêbı!'''",
        "sitejspreview": "'''Şımayê enewke tenya verqaytê kodê dosya da JavaScriptê karberi vınenê.''' \n'''hewna qayd nebı!'''",
-       "userinvalidcssjstitle": "'''Teme:''' Mewzuyê \"$1\" çıniyo.\nDosyanê be namey .css u .js'i de herfa werdiye bıgurêne, mesela herında {{ns:user}}:Foo/Vector.css'i de {{ns:user}}:Foo/vector.css bınuse.",
+       "userinvalidconfigtitle": "'''Teme:''' Mewzuyê \"$1\" çıniyo.\nDosyanê be namey .css u .js'i de herfa werdiye bıgurêne, mesela herında {{ns:user}}:Foo/Vector.css'i de {{ns:user}}:Foo/vector.css bınuse.",
        "updated": "(Rozeneya)",
        "note": "'''Not:'''",
        "previewnote": "'''Şıma bızanê ke eno yew verqayto.'''\nVurnayışê şıma hewna qeyd nêbiyê!",
        "undo-summary": "Vırnayışê $1'i [[Special:Contributions/$2|$2i]] ([[User talk:$2|Werênayış]]) peyser gırewt",
        "undo-summary-username-hidden": "Rewizyona veri $1'i hewada",
        "cantcreateaccount-text": "Hesabvıraştışê na IP adrese ('''$1''') terefê [[User:$3|$3]] kılit biyo.\n\nSebebo ke terefê $3 ra diyao ''$2''",
-       "viewpagelogs": "Qeydanê na perrer bımotne",
+       "viewpagelogs": "Qeydanê na pele bımocne",
        "nohistory": "Verorê vurnayışanê na perer çıni yo.",
        "currentrev": "Çımraviyarnayışo rocane",
        "currentrev-asof": "$1 ra tepeya çım ra viyarnayışê cı'yo peyên",
        "prefs-files": "Dosyey",
        "prefs-custom-css": "CSSê xasi",
        "prefs-custom-js": "JSê xasi",
-       "prefs-common-css-js": "CSS/JavaScript pê şablonanê peran de pay biya:",
+       "prefs-common-config": "CSS/JavaScript pê şablonanê peran de pay biya:",
        "prefs-reset-intro": "ena pele de şıma tercihanê xo şenê bıçarnê be tercihanê keyepelê ke verê coy eyar biy.\nNa game tepeya nêerziyena.",
        "prefs-emailconfirm-label": "Tesdiqiya E-posta:",
        "youremail": "E-Mail (mecbur niyo) *:",
index 2919aed..e0326de 100644 (file)
        "userjspreview": "== Pśeglěd Wašogo wužywarskego JavaScripta ==\n'''Glědaj:''' Pó składowanju musyš swójomu browseroju kazaś, aby nowu wersiju pokazał: '''Mozilla/Firefox:''' ''Strg-Shift-R'', '''Internet Explorer:''' ''Strg-F5'', '''Opera:''' ''F5'', '''Safari:''' ''Cmd-Shift-R'', '''Konqueror:''' ''F5''.",
        "sitecsspreview": "'''Źiwaj na to, až wobglědujoš se jano pśeglěd toś ten CSS.'''\n'''Njejo se hyšći składował!'''",
        "sitejspreview": "'''Źiwaj na to, až wobglědujoš se jano pśeglěd toś togo koda JavaScript.'''\n'''Njejo se hyšći składował!'''",
-       "userinvalidcssjstitle": "'''Warnowanje:''' Njeeksistěrujo šat „$1“. Pšosym mysli na to, až wužywaju .css- a .js-boki mały pismik, na pś. ''{{ns:user}}:Pśikładowa/vector.css'' město ''{{ns:user}}:Pśikładowa/Vector.css''.",
+       "userinvalidconfigtitle": "'''Warnowanje:''' Njeeksistěrujo šat „$1“. Pšosym mysli na to, až wužywaju .css- a .js-boki mały pismik, na pś. ''{{ns:user}}:Pśikładowa/vector.css'' město ''{{ns:user}}:Pśikładowa/Vector.css''.",
        "updated": "(Zaktualizěrowane)",
        "note": "'''Pokazka:'''",
        "previewnote": "'''Wobmysli, až to jo jano pśeglěd.'''\nTwóje změny hyšći njejsu składowane!",
        "prefs-files": "Dataje",
        "prefs-custom-css": "Swójski CSS",
        "prefs-custom-js": "Swójski JS",
-       "prefs-common-css-js": "Zgromadny CSS/JS za wšykne suknje:",
+       "prefs-common-config": "Zgromadny CSS/JS za wšykne suknje:",
        "prefs-reset-intro": "You can use this page to reset your preferences to the site defaults. This cannot be undone.\nMóžoš toś ten bok wužywaś, aby slědk stajił swóje nastajenja na standardne gódnoty sedła. To njedajo se anulěrowaś.",
        "prefs-emailconfirm-label": "E-mailowe wobkšuśenje:",
        "youremail": "E-mail:",
index d0ab2c9..64052ed 100644 (file)
        "userjspreview": "'''Soroho no do mongintong kono tomod diti JawaSikrip momogunonu.'''\n'''Awu po nokogompi iti!'''",
        "sitecsspreview": "'''Soroho no do mongintong kono tomod diti CSS.'''\n'''Awu po nokogompi iti!'''",
        "sitejspreview": "'''Soroho no do mongintong kono tomod diti kod JawaSikrip.'''\n'''Awu po nokogompi iti!'''",
-       "userinvalidcssjstitle": "'''Pomisunudan:''' Ingaa kulit do \"$1\".\nBobolikon pinudali .css om .js momoguno do pimato tokoro, miagal pomitanan {{ns:user}}:Foo/vector.css sobaagi do ponuli di {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Pomisunudan:''' Ingaa kulit do \"$1\".\nBobolikon pinudali .css om .js momoguno do pimato tokoro, miagal pomitanan {{ns:user}}:Foo/vector.css sobaagi do ponuli di {{ns:user}}:Foo/Vector.css.",
        "updated": "(Noinwoguan)",
        "note": "'''Pasoniba:'''",
        "previewnote": "'''Soroho no do iti nopo nga kopongintangan toomod.'''\nAwu po moti nokogompi iri nopingalanannu!",
index 5d5449a..6e18b9b 100644 (file)
        "blocked-notice-logextract": "यो प्रयोगकर्ता अच्याल प्रतिवन्धित छ।\nसब है पछा: प्रतिबन्ध लग प्रविष्टि सन्दर्भ खिलाइ तल्तिर दियीरैछ:",
        "sitecsspreview": "<strong>येइ CSSलाई तम पूर्वावलोकन मात्तरी अद्दाछ: भणिबर फाम अर:।\nयो आँजि सङ्ग्रह अरिया: आथिन। </strong>",
        "sitejspreview": "<strong>येइ जावास्क्रिप्ट कोडलाई तम पूर्वावलोकन मात्तरी अद्दाछ: भणिबर फाम अर:।\nयो आँजि सङ्ग्रह अरिया: आथिन। </strong>",
-       "userinvalidcssjstitle": "<strong>चेतावनी:</strong> यहाँ कोइपनि \"$1\" नामको खोल नाइथिन् ।\nप्रचलित .css तथा .js पानाहरूले निम्नपद शीर्षक प्रयोग गद्दान्, जस्तै {{ns:user}}:Foo/Vector.css को सट्टामी {{ns:user}}:Foo/vector.css",
+       "userinvalidconfigtitle": "<strong>चेतावनी:</strong> यहाँ कोइपनि \"$1\" नामको खोल नाइथिन् ।\nप्रचलित .css तथा .js पानाहरूले निम्नपद शीर्षक प्रयोग गद्दान्, जस्तै {{ns:user}}:Foo/Vector.css को सट्टामी {{ns:user}}:Foo/vector.css",
        "updated": "नौला",
        "note": "'''सूचना:'''",
        "previewnote": "<strong>फाम अर: कि यो यक पूर्वावलोकन मात्तरी हो।</strong>\nतमले अर्‍या फेरबदेली आँजि सङ्ग्रहित भया: आथिन!",
        "prefs-files": "फाइलहरू",
        "prefs-custom-css": "अनुकुलित CSS",
        "prefs-custom-js": "अनुकुल जाभास्क्रिप्ट",
-       "prefs-common-css-js": "साझा CSS/जाभा स्क्रिप्ट सबै कि लेखा:",
+       "prefs-common-config": "साझा CSS/जाभा स्क्रिप्ट सबै कि लेखा:",
        "prefs-reset-intro": "तम ये पृष्ठलाई आफनो अभिरुचीहरू साइट पूर्वावस्थामी फर्काउनत फर्काउन प्रयोग गद्दु सकन्छौ । तै पाछा ये लाई रद्द गद्दु सकन्छौ ।",
        "prefs-emailconfirm-label": "इ-मेल एकिन प्रक्रिया:",
        "youremail": "ईमेल",
index 4d97f4b..b11a058 100644 (file)
        "userjspreview": "'''Còsta l'é sōl 'na guardêda préma 'd salvêr al mudéfichi per pruvêr al tó JavaScript personêl. Ricôrdet che al mudéfichi în mìa incòra stêdi salvêdi!'''",
        "sitecsspreview": "'''Còsta l'é sōl 'na guardêda 'l CSS préma 'd salvêrel. Ricôrdet che al mudéfichi în mìa incòra stêdi salvêdi!'''",
        "sitejspreview": "'''Còsta l'é sōl 'na guardêda préma per pruvêr al JavaScript. Ricôrdet che al mudéfichi în mìa incòra stêdi salvêdi!'''",
-       "userinvalidcssjstitle": "'''Atensiòun:''' An gh'é nisóna skin cun al nòm \"$1\". As fà nutêr che al pàgini per i .css e .js personêl a gh'àn la préma lètra dal tétol in céch, per eşèimpi {{ns:user}}:Eşèimpi/vector.css e non {{ns:user}}:Eşèimpi/Vector.css.",
+       "userinvalidconfigtitle": "'''Atensiòun:''' An gh'é nisóna skin cun al nòm \"$1\". As fà nutêr che al pàgini per i .css e .js personêl a gh'àn la préma lètra dal tétol in céch, per eşèimpi {{ns:user}}:Eşèimpi/vector.css e non {{ns:user}}:Eşèimpi/Vector.css.",
        "updated": "(Arnuvê)",
        "note": "'''Nôta:'''",
        "previewnote": "'''Ricôrdet che còsta l'é sōl 'na guardêda préma 'd salvêr.'''\nAl tō mudéfichi în MIA incòra stêdi salvêdi.",
        "prefs-files": "File",
        "prefs-custom-css": "Adâta al CSS al tō necesitê",
        "prefs-custom-js": "Adâta al JavaScript al tō necesitê",
-       "prefs-common-css-js": "CSS/JavaScript in comûn per tóti 'l skin:",
+       "prefs-common-config": "CSS/JavaScript in comûn per tóti 'l skin:",
        "prefs-reset-intro": "Es pōl druvêr cla pàgina ché per turnêr a impustêr al preferèinsi e cambiêr còli dichiarêdi int al sît. \nL'operasiòun l'an pōl mìa èser scanşlêda.",
        "prefs-emailconfirm-label": "Cunfèirma ed la pôsta eletrônica:",
        "youremail": "E-mail:",
index 5040de9..2ebb094 100644 (file)
        "userjspreview": "'''Σας υπενθυμίζουμε ότι κάνετε απλώς έλεγχο/προεπισκόπηση του JavaScript του χρήστη -δεν το έχετε ακόμα αποθηκεύσει!'''",
        "sitecsspreview": "<strong>Θυμηθείτε ότι είναι απλώς μια προεπισκόπηση αυτού του CSS.\nΔεν έχει αποθηκευτεί ακόμα!</strong>",
        "sitejspreview": "''' Θυμηθείτε ότι κάνετε μόνο προεπισκόπηση σ'αυτόν τον κώδικα JavaScript.'' '\n'' ' Δεν τον έχετε αποθηκεύσει ακόμη!'' '",
-       "userinvalidcssjstitle": "'''Προσοχή:''' Δεν υπάρχει skin με τίτλο \"$1\". Θυμηθείτε οι προσαρμοσμένες σελίδες .css και .js χρησιμοποιούν έναν τίτλο με μικρά γράμματα, π.χ. {{ns:user}}:Foo/vector.css σε αντίθεση με το {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Προσοχή:''' Δεν υπάρχει skin με τίτλο \"$1\". Θυμηθείτε οι προσαρμοσμένες σελίδες .css και .js χρησιμοποιούν έναν τίτλο με μικρά γράμματα, π.χ. {{ns:user}}:Foo/vector.css σε αντίθεση με το {{ns:user}}:Foo/Vector.css.",
        "updated": "(Ενημερώθηκε)",
        "note": "'''Προσοχή: '''",
        "previewnote": "'''Να θυμάστε ότι αυτή είναι μόνο μια προεπισκόπηση.'''\nΟι αλλαγές σας δεν έχουν ακόμη αποθηκευτεί!",
        "prefs-files": "Αρχεία",
        "prefs-custom-css": "Προκαθορισμένη CSS",
        "prefs-custom-js": "Προσαρμοσμένη JavaScript",
-       "prefs-common-css-js": "Κοινά CSS/JavaScript για όλα τα θέματα εμφάνισης:",
+       "prefs-common-config": "Κοινά CSS/JavaScript για όλα τα θέματα εμφάνισης:",
        "prefs-reset-intro": "Μπορείτε να χρησιμοποιήσετε αυτήν την σελίδα για να επαναρρυθμίσετε τις προτιμήσεις σας στις προεπιλογές του ιστότοπου. Αυτό δεν μπορεί να αναστρεφθεί.",
        "prefs-emailconfirm-label": "Επιβεβαίωση διεύθυνσης ηλεκτρονικού ταχυδρομείου:",
        "youremail": "Διεύθυνση ηλεκτρονικού ταχυδρομείου:",
index 1ebc823..4605bad 100644 (file)
        "userjspreview": "'''Memoru ke vi nun nur provas kaj antaŭrigardas vian uzantan Ĝavaskripton, ĝi ne estas jam konservita'''",
        "sitecsspreview": "'''Konsciu ke vi nur antaŭrigardas tiun ĉi CSS.'''\n'''Ĝi ne jam estis savita!''",
        "sitejspreview": "'''Konsciu ke vi nur antaŭrigardas tiun ĉi Ĝavaskripta kodon''. ''Ĝi ne jam estis konservita''.",
-       "userinvalidcssjstitle": "'''Averto:''' Ne ekzistas etoso \"$1\".\nRememoru ke individuaj .css-aj kaj .js-aj paĝoj uzas minusklan titolon, ekz. {{ns:user}}:Foo/vector.css kontraŭe al {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Averto:''' Ne ekzistas etoso \"$1\".\nRememoru ke individuaj .css-aj kaj .js-aj paĝoj uzas minusklan titolon, ekz. {{ns:user}}:Foo/vector.css kontraŭe al {{ns:user}}:Foo/Vector.css.",
        "updated": "(Ĝisdatigita)",
        "note": "<strong>Noto:</strong>",
        "previewnote": "'''Memoru, ke ĉi tio estas nur antaŭrigardo.''' \nViaj ŝanĝoj ne ankoraŭ estas konservitaj!",
        "prefs-files": "Dosieroj",
        "prefs-custom-css": "Propra CSS",
        "prefs-custom-js": "Propra JS",
-       "prefs-common-css-js": "Komuna CSS/JS por ĉiuj etosoj:",
+       "prefs-common-config": "Komuna CSS/JS por ĉiuj etosoj:",
        "prefs-reset-intro": "Vi povas uzi ĉi tiun paĝon por restarigi viajn agordojn al la originalaj defaŭltoj.\nĈi tiel ne estus malfarebla.",
        "prefs-emailconfirm-label": "Retpoŝta konfirmado:",
        "youremail": "Retadreso:",
index d1989ac..3c5b4ab 100644 (file)
        "userjspreview": "<strong>¡Recuerda que solo estás previsualizando tu JavaScript de usuario.\n¡Aún no se ha guardado!</strong>",
        "sitecsspreview": "<strong>Recuerda que solo estás previsualizando este CSS.\n¡Aún no se ha guardado!</strong>",
        "sitejspreview": "<strong>Recuerda que solo estás previsualizando este código JavaScript.\n¡Aún no se ha guardado!</strong>",
-       "userinvalidcssjstitle": "<strong>Advertencia:</strong> no existe la apariencia «$1».\nRecuerda que las páginas personalizadas .css y .js tienen un título en minúsculas. Por ejemplo, se usa {{ns:user}}:Ejemplo/vector.css en vez de {{ns:user}}:Ejemplo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Advertencia:</strong> no existe la apariencia «$1».\nRecuerda que las páginas personalizadas .css y .js tienen un título en minúsculas. Por ejemplo, se usa {{ns:user}}:Ejemplo/vector.css en vez de {{ns:user}}:Ejemplo/Vector.css.",
        "updated": "(Actualizado)",
        "note": "<strong>Nota:</strong>",
        "previewnote": "<strong>Recuerda que esto no es más que una previsualización.</strong>\nAún no se han guardado tus cambios.",
        "prefs-files": "Archivos",
        "prefs-custom-css": "CSS personalizado",
        "prefs-custom-js": "JavaScript personalizado",
-       "prefs-common-css-js": "CSS/JavaScript compartido para todas las apariencias:",
+       "prefs-common-config": "CSS/JavaScript compartido para todas las apariencias:",
        "prefs-reset-intro": "Puedes usar esta página para restaurar los valores predeterminados de tus preferencias.\nNo podrás deshacer esta acción.",
        "prefs-emailconfirm-label": "Confirmación de correo electrónico:",
        "youremail": "Correo electrónico:",
        "thumbnail_dest_directory": "Incapaz de crear el directorio de destino",
        "thumbnail_image-type": "Tipo de imagen no contemplado",
        "thumbnail_gd-library": "Configuración de la biblioteca GD incompleta: falta la función $1",
+       "thumbnail_image-size-zero": "El tamaño del archivo de imagen aparenta ser cero.",
        "thumbnail_image-missing": "El archivo parece no existir: $1",
        "thumbnail_image-failure-limit": "Ha habido muchos intentos recientes ($1 o más) para representar esta miniatura. Inténtalo de nuevo más tarde.",
        "import": "Importar páginas",
index 152bfc4..08ab547 100644 (file)
        "userjspreview": "'''Ära unusta, et see versioon sinu isiklikust JavaScriptist on alles salvestamata!'''",
        "sitecsspreview": "'''Pea meeles, et see on vaid selle stiililehe eelvaade.'''\n'''Seda pole veel salvestatud!'''",
        "sitejspreview": "'''Pea meeles, et see on vaid selle JavaScripti-koodi eelvaade.'''\n'''Seda pole veel salvestatud!'''",
-       "userinvalidcssjstitle": "'''Hoiatus:''' Kujundust nimega \"$1\" ei ole.\nÄra unusta, et kasutaja isiklikud .css- ja .js-lehed kasutavad väiketähega algavaid nimesid, näiteks  {{ns:user}}:Juhan Julm/vector.css ja mitte {{ns:user}}:Juhan Julm/Vector.css.",
+       "userinvalidconfigtitle": "'''Hoiatus:''' Kujundust nimega \"$1\" ei ole.\nÄra unusta, et kasutaja isiklikud .css- ja .js-lehed kasutavad väiketähega algavaid nimesid, näiteks  {{ns:user}}:Juhan Julm/vector.css ja mitte {{ns:user}}:Juhan Julm/Vector.css.",
        "updated": "(Värskendatud)",
        "note": "'''Meeldetuletus:'''",
        "previewnote": "'''Ära unusta, et see on kõigest eelvaade!'''\nSinu muudatused pole veel salvestatud!",
        "prefs-files": "Failid",
        "prefs-custom-css": "kohandatud CSS",
        "prefs-custom-js": "kohandatud JavaScript",
-       "prefs-common-css-js": "Kõigi kujunduste ühine CSS/JavaScript:",
+       "prefs-common-config": "Kõigi kujunduste ühine CSS/JavaScript:",
        "prefs-reset-intro": "Sellel leheküljel saad oma eelistused lähtestada võrgukoha vaike-eelistusteks.\nToimingut ei saa hiljem tühistada.",
        "prefs-emailconfirm-label": "E-posti kinnitus:",
        "youremail": "E-posti aadress:",
index 3100c46..e375fd9 100644 (file)
        "userjspreview": "'''Gogoratu zure JavaScript kodea probatu/aurreikusten zabiltzala, oraindik ez da gorde!'''",
        "sitecsspreview": "'''Ez ahaztu zure CSS kodea aurreikusten zabiltzala.'''\n'''Oraindik gorde gabe dago!'''",
        "sitejspreview": "'''Gogoratu zure JavaScript kodea probatu/aurreikusten zabiltzala'''\n'''Oraindik ez da gorde!'''",
-       "userinvalidcssjstitle": "'''Oharra:''' Ez da \"$1\" itxura existitzen. Kontuan izan .css eta .js fitxategi pertsonalizatuen izenak letra xehez idatzi behar direla; adibidez, {{ns:user}}:Adibide/vector.css, eta ez {{ns:user}}:Adibide/Vector.css.",
+       "userinvalidconfigtitle": "'''Oharra:''' Ez da \"$1\" itxura existitzen. Kontuan izan .css eta .js fitxategi pertsonalizatuen izenak letra xehez idatzi behar direla; adibidez, {{ns:user}}:Adibide/vector.css, eta ez {{ns:user}}:Adibide/Vector.css.",
        "updated": "(Eguneratua)",
        "note": "'''Oharra:'''",
        "previewnote": "'''Gogoratu hau aurrikuspen bat dela.'''\nZure aldaketak ez dira oraindik gorde!",
        "prefs-files": "Fitxategiak",
        "prefs-custom-css": "CSS pertsonalizatua",
        "prefs-custom-js": "JS pertsonalizatua",
-       "prefs-common-css-js": "Azal mota guztietan elkarbanatutako CSS/JS:",
+       "prefs-common-config": "Azal mota guztietan elkarbanatutako CSS/JS:",
        "prefs-reset-intro": "Orrialde hau erabil dezakezu zure guneko berezko hobespenak berreskuratzeko.\nHau ezin da desegin.",
        "prefs-emailconfirm-label": "E-posta baieztapena:",
        "youremail": "E-posta:",
index 0b1ecfa..54a380d 100644 (file)
        "userjsyoucanpreview": "'''Consehu:''' Gasta el botón 'Previsoreal' pa prebal el tu nuevu JS enantis d´emburacal.",
        "usercsspreview": "'''Alcuerdati que solu estás previsoreandu el tu CSS d'usuáriu.'''\n'''Entovia nu está emburacau!'''",
        "userjspreview": "<strong>Recuerda que solu estás prebandu/previsoreandu el tu JavaScript d’usuáriu.\nEntovia nu está emburacau!</strong>",
-       "userinvalidcssjstitle": "'''Avisu:''' Nu desisti el skin \"$1\". Alcuerdati que las páhinas presonalizás .css i .js tienin el su entítulu en menúsculas, p.s. {{ns:user}}:Foo/vector.css en lugal de {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Avisu:''' Nu desisti el skin \"$1\". Alcuerdati que las páhinas presonalizás .css i .js tienin el su entítulu en menúsculas, p.s. {{ns:user}}:Foo/vector.css en lugal de {{ns:user}}:Foo/Vector.css.",
        "updated": "(Atualizau)",
        "note": "'''Nota:'''",
        "previewnote": "'''Agora solu estás previsoreandu; entovia nu están emburacaus los chambus!'''",
index 9c1f343..2e65495 100644 (file)
        "userjspreview": "'''به یاد داشته باشید که شما فقط دارید جاوااسکریپت کاربری‌تان را امتحان می‌کنید/پیش‌نمایش آن را می‌بینید.'''\n'''این جاوااسکریپت هنوز ذخیره نشده‌است!'''",
        "sitecsspreview": "'''به یاد داشته باشید که شما فقط دارید پیش‌نمایش این سی‌اس‌اس را می‌بینید.'''\n'''این سی‌اس‌اس هنوز ذخیره نشده‌است!'''",
        "sitejspreview": "'''به یاد داشته باشید که شما فقط دارید پیش‌نمایش این جاوااسکریپت را می‌بینید.'''\n'''این جاوااسکریپت هنوز ذخیره نشده‌است!'''",
-       "userinvalidcssjstitle": "'''هشدار:''' پوسته‌ای به نام «$1» وجود ندارد.\nبه یاد داشته باشید که صفحه‌های شخصی ‎.css و ‎.js باید عنوانی با حروف کوچک داشته باشند؛ نمونه: {{ns:user}}:فو/vector.css در مقابل {{ns:user}}:فو/Vector.css.",
+       "userinvalidconfigtitle": "'''هشدار:''' پوسته‌ای به نام «$1» وجود ندارد.\nبه یاد داشته باشید که صفحه‌های شخصی ‎.css و ‎.js باید عنوانی با حروف کوچک داشته باشند؛ نمونه: {{ns:user}}:فو/vector.css در مقابل {{ns:user}}:فو/Vector.css.",
        "updated": "(به‌روز شد)",
        "note": "'''نکته:'''",
        "previewnote": "'''به یاد داشته باشید که این فقط پیش‌نمایش است.'''\nتغییرات شما هنوز ذخیره نشده‌است!",
        "prefs-files": "پرونده‌ها",
        "prefs-custom-css": "سی‌اس‌اس شخصی",
        "prefs-custom-js": "جاوااسکریپت شخصی",
-       "prefs-common-css-js": "سی‌اس‌اس/جاوااسکریپت مشترک برای تمام پوسته‌ها:",
+       "prefs-common-config": "سی‌اس‌اس/جاوااسکریپت مشترک برای تمام پوسته‌ها:",
        "prefs-reset-intro": "شما می‌توانید از این صفحه برای بازگرداندن تنظیمات خود به پیش‌فرض تارنما استفاده کنید.\nاین کار بازگشت‌ناپذیر است.",
        "prefs-emailconfirm-label": "تأیید ایمیل:",
        "youremail": "ایمیل:",
index 71c593e..b648dfa 100644 (file)
        "userjspreview": "'''Tämä on JavaScriptin esikatselu.'''",
        "sitecsspreview": "'''Huomaa, että tämä on vasta CSS:n esikatselu.''' \n'''Muutoksia ei ole vielä tallennettu.'''",
        "sitejspreview": "'''Huomaa, että tämä on vasta JavaScript-koodin esikatselu.'''\n'''Muutoksia ei ole vielä tallennettu.'''",
-       "userinvalidcssjstitle": "'''Varoitus:''' Tyyliä nimeltä ”$1” ei ole olemassa. Muista, että käyttäjän määrittelemät .css- ja .js-sivut alkavat pienellä alkukirjaimella, esim. {{ns:user}}:Matti Meikäläinen/vector.css eikä {{ns:user}}:Matti Meikäläinen/Vector.css.",
+       "userinvalidconfigtitle": "'''Varoitus:''' Tyyliä nimeltä ”$1” ei ole olemassa. Muista, että käyttäjän määrittelemät .css- ja .js-sivut alkavat pienellä alkukirjaimella, esim. {{ns:user}}:Matti Meikäläinen/vector.css eikä {{ns:user}}:Matti Meikäläinen/Vector.css.",
        "updated": "(Päivitetty)",
        "note": "'''Huomautus:'''",
        "previewnote": "'''Tämä on vasta sivun esikatselu.'''\nTekemiäsi muutoksia ei ole vielä tallennettu.",
        "post-expand-template-argument-category": "Käsittelemättömiä mallinemuuttujia sisältävät sivut",
        "parser-template-loop-warning": "Mallinesilmukka havaittu: [[$1]]",
        "template-loop-category": "Sivut joilla on mallinesilmukoita",
+       "template-loop-warning": "<strong>Varoitus:</strong> Tämä sivu kutsuu mallinetta [[:$1]] joka aiheuttaa mallinesilmukan (loputon rekursiivinen kutsu).",
        "parser-template-recursion-depth-warning": "Mallineen rekursioraja ylittyi ($1)",
        "language-converter-depth-warning": "Kielimuuntimen syvyysraja ylittyi ($1)",
        "node-count-exceeded-category": "Sivut, joissa solmumäärä on ylitetty",
        "diff-multi-sameuser": "({{PLURAL:$1|Yhtä välissä olevaa versiota|$1 välissä olevaa versiota}} samalta käyttäjältä ei näytetä)",
        "diff-multi-otherusers": "({{PLURAL:$1|Yhtä välissä olevaa versiota|$1 välissä olevaa versiota}} {{PLURAL:$2|toisen käyttäjän tekemänä|$2 käyttäjän tekeminä}} ei näytetä)",
        "diff-multi-manyusers": "(Versioiden välissä on {{PLURAL:$1|yksi muu muokkaus|$1 muuta muokkausta, jotka on tehnyt {{PLURAL:$2|yksi käyttäjä|yli $2 eri käyttäjää}}}}.)",
+       "diff-paragraph-moved-tonew": "Kappale siirrettiin. Klikkaa hypätäksesi uuteen sijaintiin.",
+       "diff-paragraph-moved-toold": "Kappale siirrettiin. Klikkaa hypätäksesi vanhaan sijaintiin.",
        "difference-missing-revision": "{{PLURAL:$2|Yhtä versiota|$2 versiota}} tästä vertailusta ($1) {{PLURAL:$2|ei}} löytynyt.\n\nUseimmiten tämä johtuu vanhentuneesta vertailulinkistä poistettuun sivuun.\nLisätietoja löytyy [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} poistolokista].",
        "searchresults": "Hakutulokset",
        "searchresults-title": "Haun tulokset hakusanalle ”$1”",
        "search-external": "Ulkoinen haku",
        "searchdisabled": "Tekstihaku on poistettu toistaiseksi käytöstä suuren kuorman vuoksi. Voit käyttää alla olevaa Googlen hakukenttää sivujen etsimiseen, kunnes haku tulee taas käyttöön. <small>Huomaa, että ulkopuoliset kopiot {{GRAMMAR:genitive|{{SITENAME}}}} sisällöstä eivät välttämättä ole ajan tasalla.</small>",
        "search-error": "Haku epäonnistui: $1",
+       "search-warning": "Haun aikana tapahtui varoitus: $1",
        "preferences": "Asetukset",
        "mypreferences": "Asetukset",
        "prefs-edits": "Muokkauksia",
        "prefs-files": "Tiedostot",
        "prefs-custom-css": "Käyttäjäkohtainen CSS-tyylisivu",
        "prefs-custom-js": "Käyttäjäkohtainen JavaScript-sivu",
-       "prefs-common-css-js": "Yhteiset CSS- ja JavaScript-sivut kaikille ulkoasuille",
+       "prefs-common-config": "Yhteiset CSS- ja JavaScript-sivut kaikille ulkoasuille",
        "prefs-reset-intro": "Voit käyttää tätä sivua palauttaaksesi kaikki asetuksesi sivuston oletusasetuksiin. Tätä ei voi kumota.",
        "prefs-emailconfirm-label": "Sähköpostin varmistus",
        "youremail": "Sähköpostiosoite",
        "recentchanges-noresult": "Ei muutoksia, jotka täyttävät nämä kriteerit valitun ajanjakson aikana.",
        "recentchanges-timeout": "Tämä haku aikakatkaistiin. Saatat haluta kokeilla toisia hakuehtoja.",
        "recentchanges-network": "Teknisen virheen vuoksi tuloksia ei voitu ladata. Yritä sivun päivittämistä.",
+       "recentchanges-notargetpage": "Anna yllä sivun nimi nähdäksesi sivuun liittyvät muutokset.",
        "recentchanges-feed-description": "Tällä sivulla voi seurata tuoreita {{GRAMMAR:illative|{{SITENAME}}}} tehtyjä muutoksia.",
        "recentchanges-label-newpage": "Tämä muutos loi uuden sivun",
        "recentchanges-label-minor": "Tämä on pieni muutos",
        "sp-contributions-newonly": "Näytä vain muokkaukset, joilla on luotu sivu",
        "sp-contributions-hideminor": "Piilota pienet muutokset",
        "sp-contributions-submit": "Hae",
+       "sp-contributions-outofrange": "Tuloksia ei voi näyttää. Pyydetty IP-alue on suurempi kuin /$1 CIDR-raja.",
        "whatlinkshere": "Tänne viittaavat sivut",
        "whatlinkshere-title": "Sivut, jotka viittaavat sivulle $1",
        "whatlinkshere-page": "Sivu:",
        "ipb_blocked_as_range": "IP-osoite $1 on estetty välillisesti ja sen estoa ei voi poistaa. Se on estetty osana verkkoaluetta $2, jonka eston voi poistaa",
        "ip_range_invalid": "Virheellinen IP-alue.",
        "ip_range_toolarge": "Suuremmat osoitealue-estot kuin /$1 eivät ole sallittuja.",
+       "ip_range_exceeded": "IP-alue ylittää sen enimmäisalueen. Sallittu alue: /$1.",
        "ip_range_toolow": "IP-alueet eivät käytännöllisesti katsoen ole sallittuja.",
        "proxyblocker": "Välityspalvelinesto",
        "proxyblockreason": "IP-osoitteestasi on estetty muokkaukset, koska se on avoin välityspalvelin. Ota yhteyttä Internet-palveluntarjoajaasi tai tekniseen tukeen ja kerro heille tästä tietoturvaongelmasta.",
        "thumbnail_dest_directory": "Kohdehakemiston luominen ei onnistunut",
        "thumbnail_image-type": "Kuvamuoto ei ole tuettu",
        "thumbnail_gd-library": "GD-kirjastoa ei ole asennettu oikein. Funktio $1 puuttuu.",
+       "thumbnail_image-size-zero": "Kuvatiedoston koko näyttäisi olevan nolla.",
        "thumbnail_image-missing": "Tiedosto näyttää puuttuvan: $1",
        "thumbnail_image-failure-limit": "Tätä kuvaketta on yritetty tulkita epäonnistuneesti liian monta kertaa ($1 tai enemmän). Ole hyvä ja yritä myöhemmin uudelleen.",
        "import": "Tuo sivuja",
index d1142f8..1667b4f 100644 (file)
        "userjspreview": "'''Minst til at hetta bert er ein royndarvísing av tínum brúkara JavaScript.'''\n'''Tú hevur ikki goymt tað enn!'''",
        "sitecsspreview": "'''Minst til at hetta bert er ein royndar vísing av hesum CSS.'''\n'''Tú hevur ikki goymt tað enn!'''",
        "sitejspreview": "'''Minst til at hetta bert er ein royndar vísing av hesi JavaScript kotuni.'''\n'''Tað er ikki goymt enn!'''",
-       "userinvalidcssjstitle": "'''Ávaring:''' Tað er onki skinn \"$1\".\nTilevnaðar .css og .js síður brúka heiti sum byrja við lítlum bókstavi, t.d.  {{ns:user}}:Foo/vector.css í mun til {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Ávaring:''' Tað er onki skinn \"$1\".\nTilevnaðar .css og .js síður brúka heiti sum byrja við lítlum bókstavi, t.d.  {{ns:user}}:Foo/vector.css í mun til {{ns:user}}:Foo/Vector.css.",
        "updated": "(Dagført)",
        "note": "'''Viðmerking:'''",
        "previewnote": "<strong>Minst til at hetta bara er ein forskoðan.</strong>\nTínar broytingar eru ikki goymdar enn!",
        "prefs-files": "Fílur",
        "prefs-custom-css": "Tilpassað CSS",
        "prefs-custom-js": "Tilpassað JavaScript",
-       "prefs-common-css-js": "Møgulig CSS/JavaScript fyri allar útsjóndir:",
+       "prefs-common-config": "Møgulig CSS/JavaScript fyri allar útsjóndir:",
        "prefs-reset-intro": "Tú kanst brúka hesa síðuna til at nullstilla allar tínar valdu innstillingar, so tað kemur aftur til standard.\nTú kanst ikki angra, tá tað fyrst er gjørt.",
        "prefs-emailconfirm-label": "Vátta tína t-post adressu:",
        "youremail": "T-postur (sjálvboðið)*:",
index 4168900..f8fc0fd 100644 (file)
        "userjspreview": "<strong>Rappelez-vous que vous ne faites que visualiser ou tester votre code JavaScript.\nIl n’a pas encore été enregistré !</strong>",
        "sitecsspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser cette feuille de style. \nElle n’a pas encore été enregistrée !</strong>",
        "sitejspreview": "<strong>Rappelez-vous que vous ne faites que prévisualiser ce code JavaScript. \nIl n’a pas encore été enregistré !</strong>",
-       "userinvalidcssjstitle": "<strong>Attention :</strong> il n’existe pas d’habillage « $1 ». \nLes pages personnelles avec extensions .css et .js utilisent des titres en minuscules, par exemple {{ns:user}}:Foo/vector.css et non {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Attention :</strong> il n’existe pas d’habillage « $1 ». \nLes pages personnelles avec extensions .css et .js utilisent des titres en minuscules, par exemple {{ns:user}}:Foo/vector.css et non {{ns:user}}:Foo/Vector.css.",
        "updated": "(Mis à jour)",
        "note": "<strong>Note :</strong>",
        "previewnote": "<strong>Rappelez-vous que ce n’est qu’une prévisualisation.</strong>\nVos modifications n’ont pas encore été enregistrées !",
        "prefs-files": "Fichiers",
        "prefs-custom-css": "CSS personnalisé",
        "prefs-custom-js": "JavaScript personnalisé",
-       "prefs-common-css-js": "CSS et JavaScript communs à tous les habillages :",
+       "prefs-common-config": "CSS et JavaScript communs à tous les habillages :",
        "prefs-reset-intro": "Vous pouvez utiliser cette page pour restaurer vos préférences aux valeurs par défaut du site. Ceci ne peut pas être défait.",
        "prefs-emailconfirm-label": "Confirmation du courriel :",
        "youremail": "Courriel :",
index 44d8d52..c0de588 100644 (file)
        "userjsyoucanpreview": "'''Conseil:''' Usez le bouton \"Vue d'avance\" pour tester votre nouvelle feuille JS avant de la sauver.",
        "usercsspreview": "'''Rappelez-vous que vous êtes après regarder votre feuille CSS qu'a pas encore été sauvée!'''",
        "userjspreview": "'''Rappelez-vous que vous êtes juste après regarder ou tester votre code JavaScript qu'a pas encore été sauvé!'''",
-       "userinvalidcssjstitle": "'''Attention:''' Y a pas de style \"$1\".  Rappelez-vous qu'il faut user les petites lettres dans le sujet des pages personnelles avec les extensions .css et .js.\nExemple:  {{ns:user}}:Foo/vector.css (bon)  {{ns:user}}:Foo/Vector.css (mauvais)",
+       "userinvalidconfigtitle": "'''Attention:''' Y a pas de style \"$1\".  Rappelez-vous qu'il faut user les petites lettres dans le sujet des pages personnelles avec les extensions .css et .js.\nExemple:  {{ns:user}}:Foo/vector.css (bon)  {{ns:user}}:Foo/Vector.css (mauvais)",
        "updated": "(Renouvelé)",
        "note": "'''Notez:'''",
        "previewnote": "'''Ça ici, c'est juste une vue d'avance; les changements ont pas encore été sauvés!'''",
index bebff59..c9ea180 100644 (file)
        "userjspreview": "<strong>Rapelâd-vos que vos éte ren qu’aprés èprovar prèvêre voutron code JavaScript.\nIl est p’oncor étâ encartâ !</strong>",
        "sitecsspreview": "<strong>Rapelâd-vos que vos éte ren qu’aprés prèvêre cela fôlye CSS.\nEl est p’oncor étâye encartâye !</strong>",
        "sitejspreview": "<strong>Rapelâd-vos que vos éte ren qu’aprés prèvêre cél code JavaScript.\nIl est p’oncor étâ encartâ !</strong>",
-       "userinvalidcssjstitle": "<strong>Atencion :</strong> ègziste gins d’habelyâjo « $1 ».\nRapelâd-vos que les pâges a sè avouéc èxtensions .css et .js emplèyont de titros en petiôtes lètres, per ègzemplo {{ns:user}}:Foo/vector.css et pas {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Atencion :</strong> ègziste gins d’habelyâjo « $1 ».\nRapelâd-vos que les pâges a sè avouéc èxtensions .css et .js emplèyont de titros en petiôtes lètres, per ègzemplo {{ns:user}}:Foo/vector.css et pas {{ns:user}}:Foo/Vector.css.",
        "updated": "(Betâ a jorn)",
        "note": "<strong>Nota :</strong>",
        "previewnote": "<strong>Rapelâd-vos qu’o est ren qu’un apèrçu.</strong>\nVoutros changements sont p’oncor étâs encartâs !",
        "prefs-files": "Fichiérs",
        "prefs-custom-css": "CSS pèrsonalisâ",
        "prefs-custom-js": "JavaScript pèrsonalisâ",
-       "prefs-common-css-js": "CSS et JavaScript partagiê por tôs los habelyâjos :",
+       "prefs-common-config": "CSS et JavaScript partagiê por tôs los habelyâjos :",
        "prefs-reset-intro": "Vos pouede empleyér cela pâge por rètablir voutres prèferences a les valors per dèfôt du seto.\nCen pôt pas étre dèfêt.",
        "prefs-emailconfirm-label": "Confirmacion de l’adrèce èlèctronica :",
        "youremail": "Adrèce èlèctronica :",
index 396ff16..41501cf 100644 (file)
        "userjspreview": "'''Seenk diaram, dat det bluas en föörskau faan din JavaScript as.'''\n'''Det as noch ei seekert wurden!'''",
        "sitecsspreview": "<strong>Paase üüb! Det as bluas en föörskau faan't CSS. Det as noch ei seekert wurden!</strong>",
        "sitejspreview": "<strong>Paase üüb! Det as bluas en föörskau faan di JavaScript code. Det as noch ei seekert wurden!</strong>",
-       "userinvalidcssjstitle": "''Paase üüb:''' Skak \"$1\" jaft at ei.\nSeenk diaram, dat faan en brüker iinracht .css- an .js-sidjen mä en letjen buksteew began skel. Bispal:\n''{{ns:user}}:Münsterkjarl/vector.css'' uunsteed faan ''{{ns:user}}:Münsterkjarl/Vector.css''.",
+       "userinvalidconfigtitle": "''Paase üüb:''' Skak \"$1\" jaft at ei.\nSeenk diaram, dat faan en brüker iinracht .css- an .js-sidjen mä en letjen buksteew began skel. Bispal:\n''{{ns:user}}:Münsterkjarl/vector.css'' uunsteed faan ''{{ns:user}}:Münsterkjarl/Vector.css''.",
        "updated": "(Feranert)",
        "note": "'''Paase üüb:'''",
        "previewnote": "'''Heer könst dü sä, hü det sidj wurd skal.'''\nDet sidj as oober noch ei seekert!",
        "prefs-files": "Datein",
        "prefs-custom-css": "Salew maaget CSS",
        "prefs-custom-js": "Salew maaget JavaScript",
-       "prefs-common-css-js": "CSS / JavaScript för arke skak:",
+       "prefs-common-config": "CSS / JavaScript för arke skak:",
        "prefs-reset-intro": "Üüb detdiar sidj könst dü weder a normool iinstelangen iinracht.\nDo san jo ual iinstelangen wech.",
        "prefs-emailconfirm-label": "E-Mail gudkäänd:",
        "youremail": "E-mail:",
index 3a25100..7de676d 100644 (file)
        "userjsyoucanpreview": "<strong>Tip:</strong> Brûk de knop \"{{int:showpreview}}\" om jo nije JS te testen foar it fêstlizzen.",
        "usercsspreview": "<strong>Dit is allinne mar it oerlêzen fan jo persoanlike CSS. Hy is noch net fêstlein!</strong>",
        "userjspreview": "<strong>Tink derom: jo besjogge no jo persoanlike JavaScript. De side is net fêstlein!</strong>",
-       "userinvalidcssjstitle": "<strong>Warskôging:</strong> der is gjin skin \"$1\".\nTink derom: jo eigen .css- en .js-siden begjinne mei in lytse letter, bygelyks {{ns:user}}:Namme/vector.css ynsté fan {{ns:user}}:Namme/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Warskôging:</strong> der is gjin skin \"$1\".\nTink derom: jo eigen .css- en .js-siden begjinne mei in lytse letter, bygelyks {{ns:user}}:Namme/vector.css ynsté fan {{ns:user}}:Namme/Vector.css.",
        "updated": "(Bewurke)",
        "note": "<strong>Opmerking:</strong>",
        "previewnote": "<strong>Tink der om dat dizze side noch net fêstlein is!</strong>",
index 40b2919..b4ee44f 100644 (file)
        "userjsyoucanpreview": "'''Leid:''' Sula sábhálann tú, úsáid an cnaipe \"{{int:showpreview}}\" chun do JavaScript nua a thástáil.",
        "usercsspreview": "'''Cuimhnigh nach bhfuil seo ach réamhamharc do CSS úsáideora -\nníor sábháladh é go fóill!'''",
        "userjspreview": "'''Cuimhnigh nach bhfuil seo ach réamhamharc do JavaScript úsáideora\n- níor sábháladh é go fóill!'''",
-       "userinvalidcssjstitle": "'''Rabhadh:''' Níl an craiceann \"$1\" ann.\nCuimhnigh go n-úsáideann leathanaigh shaincheaptha .css agus .js teideal i gcás íochtar, m.sh. {{ns:user}}:Foo/vector.css i leapa {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Rabhadh:''' Níl an craiceann \"$1\" ann.\nCuimhnigh go n-úsáideann leathanaigh shaincheaptha .css agus .js teideal i gcás íochtar, m.sh. {{ns:user}}:Foo/vector.css i leapa {{ns:user}}:Foo/Vector.css.",
        "updated": "(Leasaithe)",
        "note": "'''Tabhair faoi deara:'''",
        "previewnote": "'''Cuimhnigh nach bhfuil ach réamhamharc sa leathanach seo.'''\nNíl do chuid athruithe sábháilte fós!",
index 0764f30..7f6fc3d 100644 (file)
        "userjsyoucanpreview": "'''提示:''' 存到前请用'望吖起'来测吖倷𠮶新JS 。",
        "usercsspreview": "'''注意倷单系到预览倷个人𠮶 CSS,内容哈冇保存!'''",
        "userjspreview": "'''注意倷单系到测试/预览倷个人𠮶 JavaScript,内容哈冇保存!'''",
-       "userinvalidcssjstitle": "'''警告:''' 冇\"$1\"𠮶皮肤。请记到自定义𠮶 .css 同 .js 页要用小写。就话,{{ns:user}}:Foo/vector.css 伓等同 {{ns:user}}:Foo/Vector.css。",
+       "userinvalidconfigtitle": "'''警告:''' 冇\"$1\"𠮶皮肤。请记到自定义𠮶 .css 同 .js 页要用小写。就话,{{ns:user}}:Foo/vector.css 伓等同 {{ns:user}}:Foo/Vector.css。",
        "updated": "(更新正喽)",
        "note": "'''注意:'''",
        "previewnote": "'''请记到个光系预览,内容哈冇保存!'''",
index a683acb..e249240 100644 (file)
        "userjsyoucanpreview": "'''提示:''' 存到前請用'望吖起'來測吖倷嗰新JS 。",
        "usercsspreview": "'''注意倷單係到預覽倷個人嗰 CSS,內容哈冇保存!'''",
        "userjspreview": "'''注意倷單係到測試/預覽倷個人嗰 JavaScript,內容哈冇保存!'''",
-       "userinvalidcssjstitle": "'''警告:''' 冇\"$1\"嗰皮膚。請記到自定義嗰 .css 同 .js 頁要用小寫。就話,{{ns:user}}:Foo/vector.css 伓等同 {{ns:user}}:Foo/Vector.css。",
+       "userinvalidconfigtitle": "'''警告:''' 冇\"$1\"嗰皮膚。請記到自定義嗰 .css 同 .js 頁要用小寫。就話,{{ns:user}}:Foo/vector.css 伓等同 {{ns:user}}:Foo/Vector.css。",
        "updated": "(更新正哩)",
        "note": "'''注意:'''",
        "previewnote": "'''請記到箇光係預覽,內容哈冇保存!'''",
index b30a5bd..036ef01 100644 (file)
        "userjspreview": "<strong>Cuimhnich nach e seo ach ro-shealladh/deuchainn air a' JavaScript agad.\nCha deach a shàbhaladh fhathast!</strong>",
        "sitecsspreview": "<strong>Cuimhnich nach e seo ach ro-shealladh air a' CSS agad.\nCha deach a shàbhaladh fhathast!</strong>",
        "sitejspreview": "<strong>Cuimhnich nach e seo ach ro-shealladh air còd a' JavaScript agad.\nCha deach a shàbhaladh fhathast!</strong>",
-       "userinvalidcssjstitle": "<strong>Rabhadh:</strong> Chan eil an craiceann \"$1\" ann.\nCleachdaidh duilleagan gnàthaichte .css agus .js tiotal ann an litrichean beaga, m.e. {{ns:user}}:Foo/vector.css seach {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Rabhadh:</strong> Chan eil an craiceann \"$1\" ann.\nCleachdaidh duilleagan gnàthaichte .css agus .js tiotal ann an litrichean beaga, m.e. {{ns:user}}:Foo/vector.css seach {{ns:user}}:Foo/Vector.css.",
        "updated": "(Air ùrachadh)",
        "note": "<strong>An aire:</strong>",
        "previewnote": "<strong>Cuimhnich nach eil ann ach ro-shealladh.</strong>\nCha deach na mùthaidhean agad a shàbhaladh fhathast!",
        "prefs-files": "Faidhlichean",
        "prefs-custom-css": "CSS gnàthaichte",
        "prefs-custom-js": "JavaScript gnàthaichte",
-       "prefs-common-css-js": "CSS/JavaScript ann an coitcheann do gach craiceann:",
+       "prefs-common-config": "CSS/JavaScript ann an coitcheann do gach craiceann:",
        "prefs-reset-intro": "'S urrainn dhut bun-roghainnean na làraich ath-shuidheachadh air an duilleag seo. Cha ghabh seo a neo-dhèanamh.",
        "prefs-emailconfirm-label": "Dearbhadh puist-d:",
        "youremail": "Post-dealain:",
index fb44ca6..0e7a58e 100644 (file)
        "userjspreview": "'''Lembre que só está probando/previsualizando o seu JavaScript de usuario.'''\n'''Este aínda non foi gardado!'''",
        "sitecsspreview": "'''Lembre que só está vendo a vista previa deste CSS.'''\n'''Este aínda non foi gardado!'''",
        "sitejspreview": "'''Lembre que só está vendo a vista previa deste código JavaScript.'''\n'''Este aínda non foi gardado!'''",
-       "userinvalidcssjstitle": "<strong>Aviso:</strong> Non hai ningunha aparencia chamada \"$1\".\nLembre que as páxinas .css e .js personalizadas utilizan un título en minúsculas, como por exemplo \"{{ns:user}}:Exemplo/vector.css\" no canto de \"{{ns:user}}:Exemplo/Vector.css\".",
+       "userinvalidconfigtitle": "<strong>Aviso:</strong> Non hai ningunha aparencia chamada \"$1\".\nLembre que as páxinas .css e .js personalizadas utilizan un título en minúsculas, como por exemplo \"{{ns:user}}:Exemplo/vector.css\" no canto de \"{{ns:user}}:Exemplo/Vector.css\".",
        "updated": "(Actualizado)",
        "note": "'''Nota:'''",
        "previewnote": "<strong>Lembre que esta é só unha vista previa.</strong>\nAínda non gardou os seus cambios!",
        "prefs-files": "Ficheiros",
        "prefs-custom-css": "CSS personalizado",
        "prefs-custom-js": "JavaScript personalizado",
-       "prefs-common-css-js": "CSS/JavaScript compartido por todas as aparencias:",
+       "prefs-common-config": "CSS/JavaScript compartido por todas as aparencias:",
        "prefs-reset-intro": "Pode usar esta páxina para restablecer as súas preferencias ás que veñen dadas por defecto.\nEste cambio non se poderá desfacer.",
        "prefs-emailconfirm-label": "Confirmación do correo:",
        "youremail": "Correo electrónico:",
index c9d4291..c56250e 100644 (file)
        "clearyourcache": "'''Ἐπισημείωσις - Μετὰ τὸ καταγράφειν, ἐνδεχομένως δεῖ σε παρακάμψειν τὴν λανθάνουσαν μνήμην τοῦ πλοηγητηρίου σου πρὸ τοῦ ὁρᾶν τὰς μεταβολάς.'''\n'''Mozilla Safari:''' ἐρητύειν τὸ ''Shift'' ἐνῷ θλίβεις τὸ ''Reload'', ἢ πίεσον εἴτε ''Ctrl-F5'' ἢ ''Ctrl-R'' (''Command-R'' ἐν Mac);\n'''Google Chrome:''' θλίψον ''Ctrl-Shift-R'' (''Command-Shift-R'' ἐν Mac)\n'''Konqueror: '''θλίψον τὸ ''Reload'' ἢ πίεσον ''F5''\n'''Opera:''' καθαίρειν τὴν λανθάνουσαν μνήμην ἐν ''Tools → Preferences''\n'''Internet Explorer:''' ἐρητύειν τὸ ''Ctrl'' ἐνῷ θλίβεις τὸ ''Refresh,'' ἢ πίεσον ''Ctrl-F5''.",
        "usercssyoucanpreview": "'''Βουλή:''' Χρῆσον τῷ κομβίῳ 'Δεικνύναι προθεώρησιν' ἵνα δοκιμάσῃς τὴν νέαν σου CSS πρὸ τοῦ καταγράφειν.",
        "userjsyoucanpreview": "'''Βουλή:''' Χρῆσον τῷ κομβίῳ 'Δεικνύναι προθεώρησιν' ἵνα δοκιμάσῃς τὴν νέαν σου JS πρὸ τοῦ καταγράφειν.",
-       "userinvalidcssjstitle": "'''Προσοχή:''' Οὐχ ὑφίσταται ''skin'' \"$1\". Μέμνησο: αἱ προσηρμοσμέναι δέλτοι .css καὶ .js χρῶνται ἐπώνυμον τι ἔχον πεζὰ γράμματα, π.χ. {{ns:user}}:Foo/vector.css ἐν ἀντίθεσει πρὸς τὸν {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Προσοχή:''' Οὐχ ὑφίσταται ''skin'' \"$1\". Μέμνησο: αἱ προσηρμοσμέναι δέλτοι .css καὶ .js χρῶνται ἐπώνυμον τι ἔχον πεζὰ γράμματα, π.χ. {{ns:user}}:Foo/vector.css ἐν ἀντίθεσει πρὸς τὸν {{ns:user}}:Foo/Vector.css.",
        "updated": "(Ἐνημερωθέν)",
        "note": "'''Ἐπισήμανσις:'''",
        "previewnote": "'''Ἥδε ἐστὶ προθεώρησις, οὐχὶ καταγραφὴ τῶν μεταβολῶν!'''",
index f16e09e..9c1dcfe 100644 (file)
        "userjspreview": "== Vorschau vu Dyynem Benutzer-Javascript. ==\n'''Gib acht:''' Noch em Spychere muesch Dyy Browser aawyse di nej Version z lade: '''Mozilla:''' ''Strg-Shift-R'', '''IE:''' ''Strg-F5'', '''Safari:''' ''Cmd-Shift-R'', '''Konqueror:''' ''F5''.",
        "sitecsspreview": "'''Obacht: Du luegsch nume ne Vorschau vu däm CSS aa.'''\n'''S isch nonig gspycheret wore!'''",
        "sitejspreview": "'''Obacht: Du luegsch nume ne Vorschau vu däm JavaScript aa.'''\n'''S isch nonig gspycheret wore!'''",
-       "userinvalidcssjstitle": "'''Achtig:''' D Skin „$1“ git s nid. Dänk dra, ass benutzerspezifischi .css- und .js-Syte mit eme Chleibuechstabe mien aafange, also z B. ''{{ns:user}}:Mustermann/vector.css'' statt ''{{ns:user}}:Mustermann/Vector.css''.",
+       "userinvalidconfigtitle": "'''Achtig:''' D Skin „$1“ git s nid. Dänk dra, ass benutzerspezifischi .css- und .js-Syte mit eme Chleibuechstabe mien aafange, also z B. ''{{ns:user}}:Mustermann/vector.css'' statt ''{{ns:user}}:Mustermann/Vector.css''.",
        "updated": "(Gänderet)",
        "note": "'''Obacht: '''",
        "previewnote": "'''Das isch numen e Vorschau und nonig gspycheret!'''\nDie Syte isch nonig gspycheret wore!",
        "prefs-files": "Bilder",
        "prefs-custom-css": "Benutzerdefinierti CSS",
        "prefs-custom-js": "Benutzerdefiniert JS",
-       "prefs-common-css-js": "Gmeinsam CSS/JS fir alli Skin:",
+       "prefs-common-config": "Gmeinsam CSS/JS fir alli Skin:",
        "prefs-reset-intro": "Du chasch die Syte verwände go d Yystellige uf dr Standard zrucksetze.\nDes cha nimmi ruckgängig gmacht wäre.",
        "prefs-emailconfirm-label": "E-Mail-Bstätigung:",
        "youremail": "E-Mail-Adräss:",
index a75b84c..01be1ab 100644 (file)
        "userjspreview": "'''યાદ રહે કે તમે તમારા સભ્ય JavaScript નું અવલોકન કરો છે.'''\n'''હજી સીધું તે સચવાયું નથી!'''",
        "sitecsspreview": "'''યાદ રહે કે તમે તમારા સભ્ય CSS નું અવલોકન કરો છે.'''\n'''હજી સીધું તે સચવાયું નથી!'''",
        "sitejspreview": "'''યાદ રહે કે તમે તમારા સભ્ય JavaScript નું અવલોકન કરો છે.'''\n'''હજી સીધું તે સચવાયું નથી!'''",
-       "userinvalidcssjstitle": "'''ચેતવણી:''' કોઇ પણ \"$1\" પટલ નથી.\nસભ્ય રચિત .css અને  .js પાના બીજી અંગ્રેજી બારખડી શીર્ષક વાપરે છે, દા. ત. {{ns:user}}:Foo/vector.css નહીં કે {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''ચેતવણી:''' કોઇ પણ \"$1\" પટલ નથી.\nસભ્ય રચિત .css અને  .js પાના બીજી અંગ્રેજી બારખડી શીર્ષક વાપરે છે, દા. ત. {{ns:user}}:Foo/vector.css નહીં કે {{ns:user}}:Foo/Vector.css.",
        "updated": "(સંવર્ધીત)",
        "note": "'''નોંધ:'''",
        "previewnote": "<strong>ધ્યાનમાં રાખો કે આ ફક્ત પૂર્વાવલોકન છે.</strong>\nતમારા ફેરફારો હજુ સાચવવામાં આવ્યા નથી!",
        "prefs-files": "ફાઇલ",
        "prefs-custom-css": "ખાસ  CSS",
        "prefs-custom-js": "સભ્ય નિર્મિત JavaScript",
-       "prefs-common-css-js": "બધા જ ફલક માટે સહીયારી CSS/JavaScript",
+       "prefs-common-config": "બધા જ ફલક માટે સહીયારી CSS/JavaScript",
        "prefs-reset-intro": "તમે આ પાનાનો ઉપયોગ કરીને તમારા પસંદ કરેલા વિકલ્પોને પાછા સાઇટના મૂળ વિકલ્પો સમાન ગોઠવી શકો છો.\n\nઆને ઉલટાવી નહિ શકાય.",
        "prefs-emailconfirm-label": "ઇ-મેલ પુષ્ટી",
        "youremail": "ઇ-મેઇલ:",
index c4819f7..26e605d 100644 (file)
        "userjsyoucanpreview": "'''Thì-sṳ:''' Chhai pó-chhùn chhièn chhiáng yung 'hién-sṳ yi-ko' on-néu lòi chhet-chhṳ ngì sîn-ke JS.",
        "usercsspreview": "'''Chu-yi ngì chak-he chhai yi-liau ngì ke-ngìn CSS, hàn-mò tú-chhùn!'''",
        "userjspreview": "'''記緊汝單單係在測試/預覽汝嘅用戶JavaScript。'''\n'''還吂保存!'''",
-       "userinvalidcssjstitle": "'''警告:''' 毋存在外皮“$1”。\n注意自定嘅.css撈.js頁愛使用小寫標題,例如,{{ns:user}}:Foo/vector.css撈 {{ns:user}}:Foo/Vector.css毋同。",
+       "userinvalidconfigtitle": "'''警告:''' 毋存在外皮“$1”。\n注意自定嘅.css撈.js頁愛使用小寫標題,例如,{{ns:user}}:Foo/vector.css撈 {{ns:user}}:Foo/Vector.css毋同。",
        "updated": "(Yí-kîn kiên-sîn)",
        "note": "<strong>Chu-yi:</strong>",
        "previewnote": "'''請記到邇單淨係預覽。'''\n汝嘅更改還吂保存!",
index 8f7f434..a291093 100644 (file)
        "userjspreview": "<strong>זִכרו שזו רק בדיקה/תצוגה מקדימה של סקריפט ה־JavaScript שלכם.\nהוא עדיין לא נשמר!</strong>",
        "sitecsspreview": "'''זכרו שזו רק תצוגה מקדימה של גיליון ה־CSS הזה.'''\n'''הוא טרם נשמר!'''",
        "sitejspreview": "'''זכרו שזו רק תצוגה מקדימה של קוד ה־JavaScript הזה.'''\n'''הוא טרם נשמר!'''",
-       "userinvalidcssjstitle": "'''אזהרה:''' העיצוב \"$1\" אינו קיים.\nדפי .css ו־.js מותאמים אישית משתמשים בכותרת עם אותיות קטנות – למשל, {{ns:user}}:דוגמה/vector.css ולא {{ns:user}}:דוגמה/Vector.css.",
+       "userinvalidconfigtitle": "'''אזהרה:''' העיצוב \"$1\" אינו קיים.\nדפי .css ו־.js מותאמים אישית משתמשים בכותרת עם אותיות קטנות – למשל, {{ns:user}}:דוגמה/vector.css ולא {{ns:user}}:דוגמה/Vector.css.",
        "updated": "(מעודכן)",
        "note": "'''הערה:'''",
        "previewnote": "<strong>{{GENDER:|זכור|זִכרי|זִכרו}} שזו רק תצוגה מקדימה.</strong>\nהשינויים {{GENDER:|שלך|שלך|שלכם}} עדיין לא נשמרו!",
        "prefs-files": "קבצים",
        "prefs-custom-css": "קובץ CSS מותאם אישית",
        "prefs-custom-js": "קובץ JavaScript מותאם אישית",
-       "prefs-common-css-js": "קובצי CSS/JavaScript משותפים לכל העיצובים:",
+       "prefs-common-config": "קובצי CSS/JavaScript משותפים לכל העיצובים:",
        "prefs-reset-intro": "באפשרותך להשתמש באפשרות זו כדי להחזיר את ההעדפות שלך להגדרות ברירת המחדל של האתר.\nלא ניתן לבטל פעולה זו.",
        "prefs-emailconfirm-label": "אימות כתובת דוא\"ל:",
        "youremail": "דואר אלקטרוני:",
        "thumbnail_dest_directory": "לא ניתן היה ליצור את תיקיית היעד",
        "thumbnail_image-type": "סוג התמונה אינו נתמך",
        "thumbnail_gd-library": "הגדרת הספריה GD אינה שלמה: חסרה הפונקציה $1",
+       "thumbnail_image-size-zero": "נראה שקובץ התמונה הוא בגודל אפס.",
        "thumbnail_image-missing": "נראה שהקובץ הבא חסר: $1",
        "thumbnail_image-failure-limit": "היו לאחרונה ניסיונות רבים מדי ($1 או יותר) ליצור את התמונה הממוזערת הזאת. נא לנסות שוב מאוחר יותר.",
        "import": "ייבוא דפים",
index 7e0397d..a36d1d3 100644 (file)
        "userjspreview": "'''ध्यान दें कि आप अपनी जावास्क्रिप्ट की झलक देख रहे हैं।'''\n'''यह अभी तक संजोई नहीं गई है!'''",
        "sitecsspreview": "''''ध्यान दें कि आप इस सी॰एस॰एस की झलक देख रहे हैं।'''\n'''यह अभी तक संजोई नहीं गई है!'''",
        "sitejspreview": "'''ध्यान दें कि आप इस जावास्क्रिप्ट कोड की झलक देख रहे हैं।'''\n'''यह अभी तक संजोया नहीं गया है!'''",
-       "userinvalidcssjstitle": "'''चेतावनी:''' \"$1\" नाम की कोई त्वचा नहीं है।\nबदले हुए .css और .js पृष्ठों के शीर्षक नीचे स्तर की लिपि (lowercase) का प्रयोग करते हैं। उदाहरण: {{ns:user}}:Foo/vector.css न की {{ns:user}}:Foo/Vector.css",
+       "userinvalidconfigtitle": "'''चेतावनी:''' \"$1\" नाम की कोई त्वचा नहीं है।\nबदले हुए .css और .js पृष्ठों के शीर्षक नीचे स्तर की लिपि (lowercase) का प्रयोग करते हैं। उदाहरण: {{ns:user}}:Foo/vector.css न की {{ns:user}}:Foo/Vector.css",
        "updated": "(अद्यतनीत)",
        "note": "'''सूचना:'''",
        "previewnote": "'''याद रखें, यह केवल एक झलक है।'''\nआपके बदलाव अभी तक संजोये नहीं गए हैं!",
        "prefs-files": "फ़ाइलें",
        "prefs-custom-css": "खासमखास सी॰एस॰एस",
        "prefs-custom-js": "खासमखास जावास्क्रिप्ट",
-       "prefs-common-css-js": "सभी त्वचाओं के लिए साझा सी॰एस॰एस/जावास्क्रिप्ट:",
+       "prefs-common-config": "सभी त्वचाओं के लिए साझा सी॰एस॰एस/जावास्क्रिप्ट:",
        "prefs-reset-intro": "आप इस पृष्ठ के ज़रिए अपनी वरीयताओं को साइट की मूल वरीयताओं के समान बना सकते हैं।\nइसके बाद आप वापस पुरानी स्थिति पर नहीं आ सकेंगे।",
        "prefs-emailconfirm-label": "ई-मेल पुष्टिकरण:",
        "youremail": "आपका ई-मेल पता:",
index c60a534..b96f5d0 100644 (file)
        "userjspreview": "'''Yaad rakhna ki aap khali aapan JavaScript ke testing/previewing  karta hai, iske abhi save nai karaa gais hai!'''",
        "sitecsspreview": " '''Yaad rakhna ki aap ii CSS ke khaali preview kartaa hae.'''\n'''Iske abhi talak bachawa nai gais hae!'''",
        "sitejspreview": " '''Yaad rakhna ki aap ii JavaScript code ke khaali preview kartaa hae.'''\n'''Iske abhi talak bachawa nai gais hae!'''",
-       "userinvalidcssjstitle": "'''Warning:''' Koi skin \"$1\" nai hai.\nYaad rakhna ki custom .css aur .js panna owercase title use kare hai, jaise ki {{ns:user}}:Foo/vector.css aur{{ns:user}}:Foo/Vector.css nai.",
+       "userinvalidconfigtitle": "'''Warning:''' Koi skin \"$1\" nai hai.\nYaad rakhna ki custom .css aur .js panna owercase title use kare hai, jaise ki {{ns:user}}:Foo/vector.css aur{{ns:user}}:Foo/Vector.css nai.",
        "updated": "(Update kar dewa gais hai)",
        "note": "'''Dhyan rakkho:'''",
        "previewnote": "'''Ii khaali ek jhalak dekhae hai'''\nTumar badlao abhi bachawa nai gais hai!",
        "prefs-files": "File ke naam",
        "prefs-custom-css": "CSS ke aapan khatir badlo",
        "prefs-custom-js": "Ruchi ke anusar JS",
-       "prefs-common-css-js": "Sab skins ke khatir, baata gais CSS/JavaScript",
+       "prefs-common-config": "Sab skins ke khatir, baata gais CSS/JavaScript",
        "prefs-reset-intro": "Aap ii panna ke kaam me laae ke site defaults ke aapan preferences ke reset kare sakta hai.\nIske pahile jaise nai karaa jaawe sake hai.",
        "prefs-emailconfirm-label": "E-mail ke confirm karaa jaawe hai:",
        "youremail": "E-mail:",
index 4829231..79d086a 100644 (file)
        "userjspreview": "'''Tandai nga ginalantaw/ginatilawan mo pa lang ang imo JavaScript sang manuggamit.'''\n'''Wala pa ini matipon!'''",
        "sitecsspreview": "'''Tandai nga ginalantaw mo pa lang ang ini nga CSS.'''\n'''Wala pa ini matipon!'''",
        "sitejspreview": "'''Tandai nga ginalantaw mo pa lang ang ini nga kodigo sang JavaScript.'''\n'''Wala pa ini matipon!'''",
-       "userinvalidcssjstitle": "'''Aviso:''' Wala sang panit nga \"$1\".\nSa mga pahungod nga mga panid nga .css and .js magamit sang titulo nga may gagmay nga letra (lowercase), e.g. {{ns:user}}:Foo/vector.css kontra sa {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Aviso:''' Wala sang panit nga \"$1\".\nSa mga pahungod nga mga panid nga .css and .js magamit sang titulo nga may gagmay nga letra (lowercase), e.g. {{ns:user}}:Foo/vector.css kontra sa {{ns:user}}:Foo/Vector.css.",
        "updated": "(Suno sa oras)",
        "note": "'''Pansinon:'''",
        "previewnote": "'''Tandaan nga prebyu lamang ini.'''\nWala pa nabutang ang imo nga ginbag-o!",
        "prefs-files": "Mga hilera",
        "prefs-custom-css": "Ginhungod nga CSS",
        "prefs-custom-js": "Ginhungod nga JavaScript",
-       "prefs-common-css-js": "Ginaparte nga CSS?JavaScript para sa tanan nga panit:",
+       "prefs-common-config": "Ginaparte nga CSS?JavaScript para sa tanan nga panit:",
        "prefs-reset-intro": "Indi mo magamit ang ini nga panid agod mailisan ang imo mga pagpalabi sa mga default sang site.\nIndi na ini maliwat pa.",
        "prefs-emailconfirm-label": "Paagkumperma sang e-mail:",
        "youremail": "E-mail:",
index 63b9bce..4c6e11a 100644 (file)
        "userjspreview": "'''Ne zaboravite: samo isprobavate/pregledavate svoj suradnički JavaScript, i da još nije snimljen!'''",
        "sitecsspreview": "'''Ne zaboravite ovo je samo pregled ovog CSS-a.'''\n'''Još uvijek nije sačuvan!'''",
        "sitejspreview": "'''Ne zaboravite ovo je samo pregled JavaScript kôda.'''\n'''Još uvijek nije sačuvan!'''",
-       "userinvalidcssjstitle": "'''Upozorenje:''' Nema sučelja pod imenom \"$1\". Ne zaboravite da imena stranica s .css and .js kodom počinju malim slovom, npr. {{ns:user}}:Mate/vector.css, a ne {{ns:user}}:Mate/Vector.css.",
+       "userinvalidconfigtitle": "'''Upozorenje:''' Nema sučelja pod imenom \"$1\". Ne zaboravite da imena stranica s .css and .js kodom počinju malim slovom, npr. {{ns:user}}:Mate/vector.css, a ne {{ns:user}}:Mate/Vector.css.",
        "updated": "(Ažurirano)",
        "note": "'''Napomena:'''",
        "previewnote": "<strong>Ne zaboravite da je ovo samo pregled kako će stranica izgledati.</strong>\nVaše uređivanje još nije snimljeno!",
        "prefs-files": "Datoteke",
        "prefs-custom-css": "Prilagođen CSS",
        "prefs-custom-js": "Prilagođen JS",
-       "prefs-common-css-js": "Dijeljeni CSS/JS za sve izglede:",
+       "prefs-common-config": "Dijeljeni CSS/JS za sve izglede:",
        "prefs-reset-intro": "Možete koristiti ovu stranicu za povrat Vaših postavki na prvotne postavke. Ovo se ne može poništiti.",
        "prefs-emailconfirm-label": "Potvrda e-mail adrese:",
        "youremail": "Vaša adresa e-pošte:",
index 09ee5b6..b9e8dcf 100644 (file)
        "userjspreview": "'''Beacht, dass du nuar en Voarschau von dein Benutzer-JavaScripts betrachte tust.'''\n'''Das woor noch net gespeichert!'''",
        "sitecsspreview": "'''Beachte, dass du nuar en Voarschau von das CSS betrachte tust.'''\n'''Das woard noch net gespeichert!'''",
        "sitejspreview": "'''Beacht, dass du nuar en Vorschau von das JavaScript betrachte tust.'''\n'''Das woor noch net gespeichert!'''",
-       "userinvalidcssjstitle": "'''Achtung:''' Die Benutzerowerfläch \"$1\" existiert net. Bedenke, dass benutzerspezifisch .css- und .js-Seite mit em Klenbuchstoob oonfänge müsse, also beispielsweis ''{{ns:user}}:Mustermann/vector.css'' an Stell von ''{{ns:user}}:Mustermann/Vector.css''.",
+       "userinvalidconfigtitle": "'''Achtung:''' Die Benutzerowerfläch \"$1\" existiert net. Bedenke, dass benutzerspezifisch .css- und .js-Seite mit em Klenbuchstoob oonfänge müsse, also beispielsweis ''{{ns:user}}:Mustermann/vector.css'' an Stell von ''{{ns:user}}:Mustermann/Vector.css''.",
        "updated": "(Geännert)",
        "note": "'''Hinweis:'''",
        "previewnote": "'''Dies ist nuar en Voarschau.'''\nDie Seit woard noch net gespeichert!",
        "prefs-files": "Dateie",
        "prefs-custom-css": "Benutzerdefinierte CSS",
        "prefs-custom-js": "Benutzerdefiniertes JavaScript",
-       "prefs-common-css-js": "Gemeinsames CSS/JavaScript von aller Benutzerowerfläche:",
+       "prefs-common-config": "Gemeinsames CSS/JavaScript von aller Benutzerowerfläche:",
        "prefs-reset-intro": "Du kannst die Seit verwenne, um die Einstellunge uff die Standards zurückzusetzen.\nDas kann net meh rückgängich gemacht sin.",
        "prefs-emailconfirm-label": "E-Mail-Bestätichung:",
        "youremail": "E-Mail-Adress:",
index 1a19adb..2412e0f 100644 (file)
        "userjspreview": "'''Dźiwaj na to, zo jenož swój wužiwarski JavaScript testuješ/sej wobhladuješ.'''\n'''Hišće njeje so składował!'''",
        "sitecsspreview": "'''Wobkedźbujće, zo sej jenož přehlad tutoho CSS wobhladuješ.'''\n'''Wón hišće składowany njeje!'''",
        "sitejspreview": "'''Wobkedźbujće, zo sej jenož přehlad tutoho JavaScriptoweho koda wobhladuješ.'''\n'''Wón hišće składowany njeje!'''",
-       "userinvalidcssjstitle": "'''Warnowanje:''' Drasta z mjenom „$1” njeeksistuje. Prošu mysli na to, zo wosobinske strony .css a .js titul z małym pismikom wuwziwaja, na př. {{ns:user}}:Foo/vector.css město {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Warnowanje:''' Drasta z mjenom „$1” njeeksistuje. Prošu mysli na to, zo wosobinske strony .css a .js titul z małym pismikom wuwziwaja, na př. {{ns:user}}:Foo/vector.css město {{ns:user}}:Foo/Vector.css.",
        "updated": "(Zaktualizowany)",
        "note": "'''Kedźbu:'''",
        "previewnote": "'''Wobmysl, zo to je jenož přehlad.'''\nTwoje změny hišće njejsu składowane!",
        "prefs-files": "Dataje",
        "prefs-custom-css": "Swójski CSS",
        "prefs-custom-js": "Swójski JS",
-       "prefs-common-css-js": "Zhromadny CSS/JS za w32 šaty:",
+       "prefs-common-config": "Zhromadny CSS/JS za w32 šaty:",
        "prefs-reset-intro": "You can use this page to reset your preferences to the site defaults. This cannot be undone.\nMóžeš tutu stronu wužiwać, zo by swoje nastajenja na standardne hódnoty sydła wróćo stajić. To njeda so anulować.",
        "prefs-emailconfirm-label": "E-mejlowe wobkrućenje:",
        "youremail": "E-mejl:",
index 3c6e441..4ce20c8 100644 (file)
        "userjspreview": "'''Sonje ke ou ap voye kout je sou fèy JavaScript ou ekri a, li poko anrejistre !'''",
        "sitecsspreview": "'''Sonje ke w ap voye yon kout je sou sa w ekri nan fèy CSS sa a.'''\n'''Li poko anrejistre !'''",
        "sitejspreview": "'''Sonje ke w ap voye yon kout je sou kòd JavaScript sa a.'''\n'''Li poko anrejistre !'''",
-       "userinvalidcssjstitle": "'''Atansyon :''' estil \"$1\" pa egziste. Paj pèsonalize ak ekstansyon .css epi .js yo ap gen tit/sijè an lèt miniskil, pa egzanp {{ns:user}}:Foo/vector.css men pa {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Atansyon :''' estil \"$1\" pa egziste. Paj pèsonalize ak ekstansyon .css epi .js yo ap gen tit/sijè an lèt miniskil, pa egzanp {{ns:user}}:Foo/vector.css men pa {{ns:user}}:Foo/Vector.css.",
        "updated": "(Li gen dènye vèsyon sou li)",
        "note": "'''Nòt :'''",
        "previewnote": "'''Atansyon, tèks sa a se yon kout je, li poko anrejistre !'''",
index 174e576..822d64e 100644 (file)
        "userjspreview": "'''Ne felejtsd el, hogy még csak teszteled a felhasználói JavaScriptedet, és még nincs elmentve!'''",
        "sitecsspreview": "'''Ne feledd, hogy csak a CSS előnézetét látod.'''\n'''Még nincs elmentve!'''",
        "sitejspreview": "'''Ne feledd, hogy a JavaScript-kódnak csak az előnézetét látod.'''\n'''Még nincs elmentve!'''",
-       "userinvalidcssjstitle": "'''Figyelem:''' Nincs „$1” nevű felület. A felületekhez tartozó .css/.js oldalak kisbetűvel kezdődnek, például ''{{ns:user}}:Gipsz Jakab/vector.css'' és nem ''{{ns:user}}:Gipsz Jakab/Vector.css''.",
+       "userinvalidconfigtitle": "'''Figyelem:''' Nincs „$1” nevű felület. A felületekhez tartozó .css/.js oldalak kisbetűvel kezdődnek, például ''{{ns:user}}:Gipsz Jakab/vector.css'' és nem ''{{ns:user}}:Gipsz Jakab/Vector.css''.",
        "updated": "(frissítve)",
        "note": "'''Megjegyzés:'''",
        "previewnote": "'''Ne feledd, hogy ez csak egy előnézet.''' A változtatásaid még nincsenek elmentve!",
        "prefs-files": "Fájlok",
        "prefs-custom-css": "saját CSS",
        "prefs-custom-js": "saját JS",
-       "prefs-common-css-js": "Közös CSS/JS az összes felület számára:",
+       "prefs-common-config": "Közös CSS/JS az összes felület számára:",
        "prefs-reset-intro": "Ezen a lapon állíthatod vissza a beállításaidat az oldal alapértelmezett értékeire.\nA műveletet nem lehet visszavonni.",
        "prefs-emailconfirm-label": "E-mail-cím megerősítése:",
        "youremail": "Az e-mail címed:",
index a16a994..2383ede 100644 (file)
        "userjsyoucanpreview": "'''Հուշում.''' Էջը հիշելուց առաջ օգտվեք «{{int:showpreview}}» կոճակից՝ ձեր նոր JS-նիշքը ստուգելու համար։",
        "usercsspreview": "'''Նկատի ունեցեք, որ դուք միայն նախադիտում եք ձեր մասնակցի CSS-նիշքը. այն դեռ հիշված չէ՛։'''",
        "userjspreview": "'''Նկատի ունեցեք, որ դուք միայն նախադիտում եք ձեր մասնակցի JavaScript-նիշքը. այն դեռ հիշված չէ՛։'''",
-       "userinvalidcssjstitle": "'''Զգուշացում.''' «$1» տեսք չի գտնվել։ Ի նկատի ունեցեք, որ մասնակցային .css և .js էջերը ունեն փոքրատառ անվանումներ, օր.՝ «{{ns:user}}:Ոմն/vector.css», և ոչ թե «{{ns:user}}:Ոմն/Vector.css»։",
+       "userinvalidconfigtitle": "'''Զգուշացում.''' «$1» տեսք չի գտնվել։ Ի նկատի ունեցեք, որ մասնակցային .css և .js էջերը ունեն փոքրատառ անվանումներ, օր.՝ «{{ns:user}}:Ոմն/vector.css», և ոչ թե «{{ns:user}}:Ոմն/Vector.css»։",
        "updated": "(Թարմացված)",
        "note": "'''Ծանուցում.'''",
        "previewnote": "'''Սա միայն նախադիտումն է. ձեր կատարած փոփոխությունները դեռ չե՛ն հիշվել։'''",
        "rcfilters-filtergroup-changetype": "Փոփոխության տեսակ",
        "rcfilters-filter-pageedits-label": "Էջի խմբագրումներ",
        "rcfilters-filter-newpages-label": "Նոր էջեր",
-       "rcfilters-filter-logactions-label": "Մուտ գործած գործողություններ",
+       "rcfilters-filter-logactions-label": "Մուտք գործած գործողություններ",
        "rcfilters-filtergroup-lastRevision": "Ամենավերջին տարբերակ",
        "rcfilters-filter-previousrevision-label": "Ոչ վերջին տարբերակ",
        "rcfilters-view-tags": "Պիտակված խմբագրումներ",
index 7703ba2..fbb1bec 100644 (file)
        "userjspreview": "'''Non oblida que isto es solmente un test/previsualisation de tu JavaScript personalisate.'''\n'''Illo non ha ancora essite salveguardate!'''",
        "sitecsspreview": "'''Non oblida que isto es solmente un previsualisation de iste CSS.'''\n'''Le modificationes non ha ancora essite salveguardate!'''",
        "sitejspreview": "'''Non oblida que isto es solmente un previsualisation de iste codice JavaScript.'''\n'''Le modificationes non ha ancora essite salveguardate!'''",
-       "userinvalidcssjstitle": "'''Attention:''' Le apparentia \"$1\" non existe.\nMemora que le paginas .css and .js personalisate usa un titulo in minusculas, p.ex. {{ns:user}}:Foo/vector.css e non {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Attention:''' Le apparentia \"$1\" non existe.\nMemora que le paginas .css and .js personalisate usa un titulo in minusculas, p.ex. {{ns:user}}:Foo/vector.css e non {{ns:user}}:Foo/Vector.css.",
        "updated": "(Actualisate)",
        "note": "'''Nota:'''",
        "previewnote": "'''Isto es solmente un previsualisation.'''\nLe modificationes non ha ancora essite publicate!",
        "prefs-files": "Files",
        "prefs-custom-css": "CSS personalisate",
        "prefs-custom-js": "JS personalisate",
-       "prefs-common-css-js": "CSS/JS commun a tote le apparentias:",
+       "prefs-common-config": "CSS/JS commun a tote le apparentias:",
        "prefs-reset-intro": "Iste pagina es pro reinitialisar tu preferentias al valores predefinite del sito.\nLe operation non pote esser disfacite.",
        "prefs-emailconfirm-label": "Confirmation del e-mail:",
        "youremail": "E-mail:",
index 62e0e97..1525a1f 100644 (file)
        "userjspreview": "'''Ingatlah bahwa yang Anda lihat hanyalah pratayang JavaScript Anda, dan bahwa pratayang tersebut belum disimpan!'''",
        "sitecsspreview": "'''Ingatlah bahwa Anda hanya menampilkan pratayang dari CSS ini.'''\n'''Perubahan belum disimpan!'''",
        "sitejspreview": "'''Ingatlah bahwa Anda hanya menampilkan pratayang dari kode JavaScript ini.'''\n'''Perubahan belum disimpan!'''",
-       "userinvalidcssjstitle": "'''Peringatan:''' Kulit \"$1\" tidak ditemukan. Harap diingat bahwa halaman .css dan .js menggunakan huruf kecil, contoh {{ns:user}}:Foo/vector.css dan bukannya {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Peringatan:''' Kulit \"$1\" tidak ditemukan. Harap diingat bahwa halaman .css dan .js menggunakan huruf kecil, contoh {{ns:user}}:Foo/vector.css dan bukannya {{ns:user}}:Foo/Vector.css.",
        "updated": "(Diperbarui)",
        "note": "'''Catatan:'''",
        "previewnote": "'''Ingatlah bahwa ini hanya pratayang.'''\nPerubahan Anda belum disimpan!",
        "prefs-files": "Berkas",
        "prefs-custom-css": "CSS pribadi",
        "prefs-custom-js": "JS pribadi",
-       "prefs-common-css-js": "CSS/JS berbagi untuk semua kulit:",
+       "prefs-common-config": "CSS/JS berbagi untuk semua kulit:",
        "prefs-reset-intro": "Anda dapat menggunakan halaman ini untuk mengembalikan preferensi Anda ke setelan baku situs.\nPengembalian preferensi tidak dapat dibatalkan.",
        "prefs-emailconfirm-label": "Konfirmasi surel:",
        "youremail": "Surel:",
index 4acbcf7..6b155aa 100644 (file)
        "userjsyoucanpreview": "'''Punta:''' Usa li buton \"{{int:showpreview}}\" por provar tui nov JavaScript ante de conservar.",
        "usercsspreview": "'''Memora que vu es solmen vident un prevision de tui CSS de usator.'''\n'''It ne have esset conservat ancor!'''",
        "userjspreview": "'''Memora que vu es solmen provant/monstrant tui JavaScript de usator.'''\n'''It ne ha esset conservat ancor!'''",
-       "userinvalidcssjstitle": "'''Advertiment:''' Ne vi pelle \"$1\".\nMemora que hábitu .css e págines .js usa un titul plu bass, e.g. {{ns:user}}:Foo/vector.css quam oposit por {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Advertiment:''' Ne vi pelle \"$1\".\nMemora que hábitu .css e págines .js usa un titul plu bass, e.g. {{ns:user}}:Foo/vector.css quam oposit por {{ns:user}}:Foo/Vector.css.",
        "updated": "(Modernisat)",
        "note": "<strong>Note:</strong>",
        "previewnote": "'''Memora se que ti es solmen un prevision.'''\nTui changes ancor ne ha esset conservat!",
index abed7eb..679152d 100644 (file)
        "userjspreview": "<strong>Laglagipem a subsubokam/ipadpadasmo ti bukodmo a JavaScript ti agar-aramat.\nSaan pay a naidulin!</strong>",
        "sitecsspreview": "<strong>Laglagipem nga ipadpadasmo laeng daytoy a CSS.\nSaan pay a naidulin!</strong>",
        "sitejspreview": "<strong>Laglagipem nga ipadpadasmo laeng daytoy a kodigo ti JavaScript.\nSaan pay nga naidulin!</strong>",
-       "userinvalidcssjstitle": "<strong>Ballaag:</strong> Awan ti kudil a \"$1\".\nDagiti panid ti naiduma a .css ken .js ket agus-usar iti titulo ti bassit a letra, kas ti {{ns:user}}:Foo/vector.css saan a kas ti {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Ballaag:</strong> Awan ti kudil a \"$1\".\nDagiti panid ti naiduma a .css ken .js ket agus-usar iti titulo ti bassit a letra, kas ti {{ns:user}}:Foo/vector.css saan a kas ti {{ns:user}}:Foo/Vector.css.",
        "updated": "(Napabaro)",
        "note": "<strong>Nota:</strong>",
        "previewnote": "<strong>Laglagipem a daytoy ket panagipadas laeng.</strong>\nDagiti sinukatam ket saan pay a naidulin!",
        "prefs-files": "Dagiti papeles",
        "prefs-custom-css": "Naiduma a CSS",
        "prefs-custom-js": "Naiduma a JavaScript",
-       "prefs-common-css-js": "Bingay a CSS/JavaScript para kadagiti amin a kudil:",
+       "prefs-common-config": "Bingay a CSS/JavaScript para kadagiti amin a kudil:",
        "prefs-reset-intro": "Mabalinmo nga usaren daytoy a panid tapno maisublim dagita kakaykayatam iti kasisigud iti daytoy a wiki.\nNgem saanto a mabalinen nga ipasubli.",
        "prefs-emailconfirm-label": "Pammasingked ti esurat:",
        "youremail": "Esurat:",
index 648c280..f679835 100644 (file)
        "yourtext": "Vua texto",
        "storedversion": "Gardita versiono",
        "editingold": "'''EGARDEZ: Vu redaktas anciena versiono di ca pagino.\nSe vu gardus ol, la chanji facita pos ita revizo perdesos.'''",
+       "unicode-support-fail": "Semblas ke vua retnavigilo ne suportas Unicode. To bezonesas por redaktar ica pagino e, pro to, vua redakto ne konservesis.",
        "yourdiff": "Diferi",
        "copyrightwarning": "Voluntez memorar ke omna kontributi a {{SITENAME}} esas sub la $2 (Videz $1 por detali).\nSe vu ne deziras ke altri modifikez vua artikli od oli distributesez libere, lore voluntez ne skribar oli hike.<br />\nPublikigante vua skribajo hike, vu asertas ke olu skribesis da vu ipsa o kopiesis de libera fonto.\n'''NE SENDEZ ARTIKLI KUN ''COPYRIGHT'' SEN PERMISO!'''",
        "protectedpagewarning": "<strong>Averto: Ica pagino esas protektita por ke nur uzeri kun administero-yuri povas redaktar ol.</strong>\nLa maxim recenta en-registrago provizesas:",
        "recreate-moveddeleted-warn": "<strong>Atencez: Vu rikreos pagino qua antee efacesis.</strong>\n\nVu bezonas konsiderar se esas konvenanta durar lua redakto, o ne.\nPor konveno, la motivo dil antea efaco montresas hike:",
        "moveddeleted-notice": "Ica pagino efacabis.\nL'efaco-registraro e la movo-registraro di la pagino povas videsar sequante, por konsulto.",
        "edit-conflict": "Konflikto di editi.",
+       "postedit-confirmation-saved": "Vua redakto konservesis",
        "content-model-wikitext": "texto Wiki",
        "content-model-javascript": "JavaScript",
        "content-json-empty-object": "vakua objekto",
        "recentchanges-label-plusminus": "La pagino modifikesis segun ica quanto di *bicoki",
        "recentchanges-legend-heading": "<strong>Noto:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (videz anke la [[Special:NewPages|listo di nova pagini]])",
+       "rcfilters-other-review-tools": "Altra instrumenti por revizo",
+       "rcfilters-activefilters": "Agiva filtrili",
        "rcfilters-filter-user-experience-level-experienced-description": "Plu kam 30 dii di agemeso e 500 redakti.",
+       "rcfilters-filter-humans-label": "Homala (ne 'bot')",
+       "rcfilters-filter-pageedits-label": "Redakti di pagini",
+       "rcfilters-filter-logactions-label": "Agadi enrejistrata",
        "rcnotefrom": "Infre {{PLURAL:$5|esas la chanjo|esas la chanji}} de <strong>$3, $4</strong> (montrata til <strong>$1</strong>).",
        "rclistfrom": "Montrar nova chanji startante de $3 $2",
        "rcshowhideminor": "$1 mikra redakti",
        "delete-confirm": "Efacar \"$1\"",
        "delete-legend": "Efacar",
        "historywarning": "<strong>Averto:</strong> La pagino quan vu efaceskas havas historio kun $1 {{PLURAL:$1|revizo|revizi}}:",
+       "confirmdeletetext": "Vu selektis efacar ica pagino komplete, inkluzite omna modifiki en ol e la kronologio di la modifiki.\n\nVoluntez konfirmar ke vu fakte deziras facar to, ke vu komprenas omna konsequi dil efaco, e ke vu efacos ol segun [[{{MediaWiki:Policy-url}}|la normi pri l'efaco di artikli]].",
        "actioncomplete": "Ago kompletigita",
        "deletedtext": "\"$1\" efacesis.\nVidez $2 por obtenar registro di recenta efaci.",
        "dellogpage": "Efaco-registraro",
        "sp-contributions-toponly": "Montrar nur la maxim recenta revizi",
        "sp-contributions-newonly": "Montrar nur redakti qui esas kreado di pagini",
        "sp-contributions-submit": "Serchez",
-       "whatlinkshere": "Quo ligas hike",
+       "whatlinkshere": "Quo ligesas adhike",
        "whatlinkshere-title": "Pagini qui ligas ad \"$1\"",
        "whatlinkshere-page": "Pagino:",
        "linkshere": "Ca pagini esas ligilizita ad '''[[:$1]]''':",
        "reblock-logentry": "modifikis la tempo di blokuso [[$1]] por durado di $2 $3",
        "unblocklogentry": "desblokusis \"$1\"",
        "block-log-flags-nocreate": "ne povas krear konto",
+       "block-log-flags-noemail": "e-posto blokuzita",
        "ipb_expiry_invalid": "Nevalida expiro-tempo.",
        "ip_range_invalid": "Nevalida IP-rango.",
        "proxyblocker": "Blokuso di 'Proxy'",
        "movepagebtn": "Movar pagino",
        "pagemovedsub": "Rinomizita sucese",
        "movepage-moved": "'''\"$1\" esas movata ad \"$2\"'''",
+       "movepage-moved-redirect": "Kreita ridirekto.",
+       "movepage-moved-noredirect": "La kreado di ridirekto nuligesis.",
        "articleexists": "Pagino kun sama nomo ja existas od la nomo\nqua vu selektis ne esas valida.\nVoluntez selektar altra nomo.",
        "movetalk": "Rinomizar la debato-pagino se to esas aplikebla.",
        "movelogpage": "Movo-registraro",
        "logentry-delete-restore": "$1 {{GENDER:$2|restauris}} la pagino $3 ($4)",
        "logentry-delete-revision": "$1 {{GENDER:$2|modifikis}} videbleso di {{PLURAL:$5|la revizo|$5 revizi}} di la pagino $3: $4",
        "revdelete-content-hid": "celita kontenajo",
+       "logentry-block-block": "$1 {{GENDER:$2|blokuzis}} {{GENDER:$4|$3}} dum $5 $6",
        "logentry-move-move": "$1 {{GENDER:$2|movis}} la pagino $3 a $4",
        "logentry-move-move-noredirect": "$1 {{GENDER:$2|movis}} la pagino $3 a $4 sen lasar ridirektilo",
        "logentry-move-move_redir": "$1 {{GENDER:$2|movis}} la pagino $3 a $4 sen lasar ridirekto",
index 3a54234..6cd24ec 100644 (file)
        "userjspreview": "'''Mundu að þú ert aðeins að prófa/forskoða JavaScript-kóðann þinn.'''\n'''Hann hefur ekki enn verið vistaður!'''",
        "sitecsspreview": "'''Mundu að þú ert aðeins að forskoða CSS-kóðann þinn.'''\n'''Hann hefur ekki enn verið vistaður!'''",
        "sitejspreview": "'''Mundu að þú ert aðeins að prófa/forskoða JavaScript-kóðann.'''\n'''Hann hefur ekki enn verið vistaður!'''",
-       "userinvalidcssjstitle": "<strong>Viðvörun:</strong> Skinnið \"$1\" er ekki til. Sérsniðin CSS og JavaScript útlit nota lágstafi, t.d.  {{ns:user}}:Foo/vector.css en alls ekki {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Viðvörun:</strong> Skinnið \"$1\" er ekki til. Sérsniðin CSS og JavaScript útlit nota lágstafi, t.d.  {{ns:user}}:Foo/vector.css en alls ekki {{ns:user}}:Foo/Vector.css.",
        "updated": "(Uppfært)",
        "note": "'''Athugið:'''",
        "previewnote": "'''Það sem sést hér er aðeins forskoðun og hefur ekki enn verið vistað!'''",
        "prefs-files": "Skrár",
        "prefs-custom-css": "Sérsniðið CSS-útlit",
        "prefs-custom-js": "Sérsniðin JavaScript",
-       "prefs-common-css-js": "Sameiginleg CSS/JavaScript fyrir öll skinn:",
+       "prefs-common-config": "Sameiginleg CSS/JavaScript fyrir öll skinn:",
        "prefs-reset-intro": "Þessi síða er til að endurstilla kjörstillingarnar í sjálfgefin gildi.\nEkki er hægt að taka þessa breytingu til baka.",
        "prefs-emailconfirm-label": "Staðfesting netfangs:",
        "youremail": "Netfang:",
index 44c3da9..bc2dba8 100644 (file)
                        "Pierpao",
                        "Sakretsu",
                        "Yiyi",
-                       "Manvydasz"
+                       "Manvydasz",
+                       "S4b1nuz E.656"
                ]
        },
        "tog-underline": "Sottolinea i collegamenti:",
        "userjspreview": "'''Questa è solo un'anteprima per provare il proprio JavaScript personale; le modifiche non sono ancora state salvate!'''",
        "sitecsspreview": "Questa è solo un'anteprima del CSS. Le modifiche non sono ancora state salvate!'''",
        "sitejspreview": "Questa è solo un'anteprima per provare il JavaScript; le modifiche non sono ancora state salvate!'''",
-       "userinvalidcssjstitle": "<strong>Attenzione:</strong> non esiste alcun tema con nome \"$1\". Si noti che le pagine per i .css e .js personalizzati hanno l'iniziale del titolo minuscola, ad esempio {{ns:user}}:Esempio/vector.css e non {{ns:user}}:Esempio/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Attenzione:</strong> non esiste alcun tema con nome \"$1\". Si noti che le pagine per i .css e .js personalizzati hanno l'iniziale del titolo minuscola, ad esempio {{ns:user}}:Esempio/vector.css e non {{ns:user}}:Esempio/Vector.css.",
        "updated": "(Aggiornato)",
        "note": "'''Nota:'''",
        "previewnote": "'''Ricorda che questa è solo un'anteprima.'''\nLe tue modifiche NON sono ancora state salvate!",
        "timezoneregion-indian": "Oceano Indiano",
        "timezoneregion-pacific": "Oceano Pacifico",
        "allowemail": "Consenti ad altri utenti di inviarmi email",
+       "email-allow-new-users-label": "Consenti email da nuovi utenti",
        "email-blacklist-label": "Impedisci a questi utenti di inviarmi email:",
        "prefs-searchoptions": "Ricerca",
        "prefs-namespaces": "Namespace",
        "prefs-files": "File",
        "prefs-custom-css": "CSS personalizzato",
        "prefs-custom-js": "JavaScript personalizzato",
-       "prefs-common-css-js": "CSS/JavaScript condiviso per tutti i temi:",
+       "prefs-common-config": "CSS/JavaScript condiviso per tutti i temi:",
        "prefs-reset-intro": "È possibile usare questa pagina per reimpostare le proprie preferenze a quelle predefinite del sito.\nL'operazione non può essere annullata.",
        "prefs-emailconfirm-label": "Conferma dell'email:",
        "youremail": "Indirizzo email:",
        "recentchanges-legend": "Opzioni ultime modifiche",
        "recentchanges-summary": "Questa pagina presenta le modifiche più recenti ai contenuti del sito.",
        "recentchanges-noresult": "Nessuna modifica durante il periodo inserito che soddisfa questi criteri.",
+       "recentchanges-timeout": "Questa ricerca è scaduta. Potresti voler provare diversi parametri di ricerca.",
        "recentchanges-network": "A causa di un errore tecnico, non è possibile caricare alcun risultato. Aggiorna la pagina.",
        "recentchanges-notargetpage": "Inserisci sopra il nome di una pagina per vedere le modifiche relative a quella pagina.",
        "recentchanges-feed-description": "Questo feed riporta le modifiche più recenti ai contenuti del sito.",
        "rcfilters-view-namespaces-tooltip": "Filtra risultati per namespace",
        "rcfilters-view-tags-tooltip": "Filtra risultati per etichette di modifica",
        "rcfilters-view-return-to-default-tooltip": "Torna al menu filtri principale",
+       "rcfilters-view-tags-help-icon-tooltip": "Ulteriori informazioni sulle modifiche etichettate",
        "rcfilters-liveupdates-button": "Aggiornamenti in tempo reale",
        "rcfilters-liveupdates-button-title-on": "Disabilita gli aggiornamenti in tempo reale",
        "rcfilters-liveupdates-button-title-off": "Mostra le nuove modifiche appena avvengono",
        "confirmrecreate": "L'utente [[User:$1|$1]] ([[User talk:$1|discussioni]]) ha cancellato questa pagina dopo che hai iniziato a modificarla, per il seguente motivo: ''$2''\nPer favore, conferma che vuoi veramente ricreare questa pagina.",
        "confirmrecreate-noreason": "L'utente [[User:$1|$1]] ([[User talk:$1|discussioni]]) {{GENDER:$1|ha cancellato}} questa pagina dopo che hai iniziato a modificarla. Per favore, conferma che vuoi veramente ricreare questa pagina.",
        "recreate": "Ricrea",
+       "confirm-purge-title": "Purga questa pagina",
        "confirm_purge_button": "OK",
        "confirm-purge-top": "Vuoi pulire la cache di questa pagina?",
        "confirm-purge-bottom": "Ripulire la cache di una pagina consente di mostrare la sua versione più aggiornata.",
        "pagelang-reason": "Motivo",
        "pagelang-submit": "Invia",
        "pagelang-nonexistent-page": "La pagina $1 non esiste.",
+       "pagelang-unchanged-language": "La pagina $1 è già impostata alla lingua $2.",
+       "pagelang-unchanged-language-default": "La pagina $1 è gia impostata alla lingua del contenuto predefinito del wiki.",
        "pagelang-db-failed": "Il database non è stato in grado di modificare la lingua della pagina.",
        "right-pagelang": "Modifica la lingua della pagina",
        "action-pagelang": "modificare la lingua della pagina",
index 0381d19..dea01b8 100644 (file)
        "userjspreview": "<strong>利用者JavaScriptを試験/プレビューしているだけに過ぎません。</strong>\n<strong>まだ保存されていません!</strong>",
        "sitecsspreview": "<strong>ここでは、CSSをプレビューしているだけに過ぎません。</strong>\n<strong>まだ保存されていません!</strong>",
        "sitejspreview": "<strong>ここでは、JavaScriptをプレビューしているだけに過ぎません。</strong>\n<strong>まだ保存されていません!</strong>",
-       "userinvalidcssjstitle": "<strong>警告:</strong>「$1」という外装はありません。\nカスタム .css/.js ページではページ名を小文字にしてください。例: {{ns:user}}:Hoge/Vector.css ではなく {{ns:user}}:Hoge/vector.css",
+       "userinvalidconfigtitle": "<strong>警告:</strong>「$1」という外装はありません。\nカスタム .css/.js ページではページ名を小文字にしてください。例: {{ns:user}}:Hoge/Vector.css ではなく {{ns:user}}:Hoge/vector.css",
        "updated": "(更新)",
        "note": "<strong>お知らせ:</strong>",
        "previewnote": "<strong>これはプレビューです。</strong>\n変更内容はまだ保存されていません!",
        "prefs-files": "ファイル",
        "prefs-custom-css": "カスタムCSS",
        "prefs-custom-js": "カスタムJavaScript",
-       "prefs-common-css-js": "すべての外装に共通のCSSとJavaScript:",
+       "prefs-common-config": "すべての外装に共通のCSSとJavaScript:",
        "prefs-reset-intro": "このページを使用すると、自分の個人設定をこのサイトの初期設定に戻せます。\nこの操作は取り消せません。",
        "prefs-emailconfirm-label": "メールアドレスの確認:",
        "youremail": "メールアドレス:",
index f7a4fd1..198dd1d 100644 (file)
        "userjsyoucanpreview": "'''Tip:''' Yuuz di \"{{int:showpreview}}\" botn fi tes yu nyuu JavaScript bifuo yu sieb.",
        "usercsspreview": "'''Memba se yu onggl a priivyuu yu yuuza CSS.'''\n'''Ino sieb yet!'''",
        "userjspreview": "'''Memba se yu onggl a tes/priivyuu yu yuuza JavaScript.'''\n'''Ino sieb yet!'''",
-       "userinvalidcssjstitle": "'''Waanin:''' No skin \"$1\" no de.\nMemba se kostom .css ahn .js piej yuuz a luwakies taikl, e.g. {{ns:user}}:Foo/vector.css az opuoz tu {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Waanin:''' No skin \"$1\" no de.\nMemba se kostom .css ahn .js piej yuuz a luwakies taikl, e.g. {{ns:user}}:Foo/vector.css az opuoz tu {{ns:user}}:Foo/Vector.css.",
        "updated": "(Opdiet)",
        "note": "'''Nuot:'''",
        "previewnote": "'''Memb se dis a onggl priivyuu.'''\nYu chienjdem no sieb yet!",
index 37b61f1..82ace80 100644 (file)
        "prefs-files": "File",
        "prefs-custom-css": "Personli CSS",
        "prefs-custom-js": "Personli JavaScript",
-       "prefs-common-css-js": "Fælls CSS/JS for åll utsienje:",
+       "prefs-common-config": "Fælls CSS/JS for åll utsienje:",
        "prefs-reset-intro": "Du kan brug siden te å tebagstell åll din instellenge te standardinstellenger.\nDä kan ett djendjörs.",
        "prefs-emailconfirm-label": "Bekräftels å e-mail:",
        "youremail": "E-mail:",
index b67539c..2307512 100644 (file)
        "userjspreview": "'''Pèngeten yèn sing panjenengan pirsani namung pratilik JavaScript panjenengan, lan menawa pratilik iku dèrèng kasimpen!'''",
        "sitecsspreview": "<strong>Élinga yèn Sampéyan mung mratuduh CSS iki.\nIki durung kasimpen!</strong>",
        "sitejspreview": "<strong>Élinga yèn Sampéyan mung mratuduh kodhé JavaScript iki.\nIki durung kasimpen!</strong>",
-       "userinvalidcssjstitle": "<strong>Pènget:</strong> Ora ana ules \"$1\".\nKaca .css lan .js padatan nganggo sesirah mawa huruf cilik, contoné {{ns:user}}:Foo/vector.css, dudu {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Pènget:</strong> Ora ana ules \"$1\".\nKaca .css lan .js padatan nganggo sesirah mawa huruf cilik, contoné {{ns:user}}:Foo/vector.css, dudu {{ns:user}}:Foo/Vector.css.",
        "updated": "(Kaanyaran)",
        "note": "<strong>Cathetan:</strong>",
        "previewnote": "<strong>Élinga yèn iki mung pratuduh.</strong>\nOwahanmu durung kasimpen!",
        "prefs-files": "Barkas",
        "prefs-custom-css": "CSS priangga",
        "prefs-custom-js": "JavaScript priangga",
-       "prefs-common-css-js": "CSS/JS barengan kabèh ules:",
+       "prefs-common-config": "CSS/JS barengan kabèh ules:",
        "prefs-reset-intro": "Panjenengan bisa migunakaké kaca iki kanggo mbalèkaké préferensi panjenengan marang setèlan baku situs.\nPembalikan ora bisa dibatalaké.",
        "prefs-emailconfirm-label": "Konfirmasi layang-èl:",
        "youremail": "Layang-èl:",
index 58fc69d..12d4f00 100644 (file)
        "userjspreview": "'''გახსოვდეთ, რომ თქვენ მხოლოდ ტესტირებას უკეთებთ ან აკვირდებით წინასწარ ხედს თქვენი მომხმარებლის ჯავასკრიპტს - ის ჯერ არ არის დამახსოვრებული!'''",
        "sitecsspreview": "'''გახსოვდეთ, რომ თქვენ ამ CSS-ის მხოლოდ წინასწარ გადახედვას უყურებთ.'''\n'''ის ჯერ არ არის დამახსოვრებული!'''",
        "sitejspreview": "'''გახსოვდეთ, რომ თქვენ ამ JavaScript კოდის მხოლოდ წინასწარ გადახედვას უყურებთ.'''\n'''ის ჯერ არ არის დამახსოვრებული!'''",
-       "userinvalidcssjstitle": "'''ყურადღება:''' გაფორმების თემა «$1» არ არის ნაპოვნი. გახსოვდეთ, რომ .css და .js გვერდებს უნდა ჰქონდეს მხოლოდ ზეხაზური სათაური, მაგ. «{{ns:user}}:ვიღაცა/vector.css», და არა «{{ns:user}}:ვიღაცა/Vector.css».",
+       "userinvalidconfigtitle": "'''ყურადღება:''' გაფორმების თემა «$1» არ არის ნაპოვნი. გახსოვდეთ, რომ .css და .js გვერდებს უნდა ჰქონდეს მხოლოდ ზეხაზური სათაური, მაგ. «{{ns:user}}:ვიღაცა/vector.css», და არა «{{ns:user}}:ვიღაცა/Vector.css».",
        "updated": "(განახლდა)",
        "note": "'''შენიშვნა:'''",
        "previewnote": "'''დაიმახსოვრეთ, ეს მხოლოდ წინასწარი გადახედვაა.'''\nთქვენი ცვლილებები ჯერ არ შენახულა!",
        "prefs-files": "ფაილები",
        "prefs-custom-css": "მომხმარებლის CSS",
        "prefs-custom-js": "მომხმარებლის JS",
-       "prefs-common-css-js": "ზოგადი CSS/JS ყველა თემისთვის:",
+       "prefs-common-config": "ზოგადი CSS/JS ყველა თემისთვის:",
        "prefs-reset-intro": "ეს გვერდი შეიძლება გამოყენებული იქნეს თქვენი კონფიგურაციის შესაცვლელად საწყის კონფიგურაციაზე. ამ მოქმედების დადასტურების შემთხვევაში, თქვენ ვეღარ შეძლებთ მის გაუქმებას.",
        "prefs-emailconfirm-label": "ელ-ფოსტის დადასტურება:",
        "youremail": "ელექტრონული ფოსტა:",
index e47ef2c..79e1c1a 100644 (file)
        "userjspreview": "'''Smekti belli aql-ak tɛerḍeḍ JavaScript inek kan, mazal ur yettusmekti ara!'''",
        "sitecsspreview": "'''Smekti belli aql-ak tɛerḍeḍ asebter CSS agi inek kan.'''\n'''Mazal ur yettusmekti ara!'''",
        "sitejspreview": "'''Smekti belli aql-ak tɛerḍeḍ angal agi JavaScript inek kan.'''\n'''Mazal ur yettusmekti ara!'''",
-       "userinvalidcssjstitle": "'''Aɣtal:''' Aglim \"$1\" ulac-it. Ur tettuḍ ara belli isebtar \".css\" d \".js\" i txedmeḍ sseqdacen azwel i yesɛan isekkilen imecṭuḥen, s umedya: {{ns:user}}:Foo/vector.css akk d {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Aɣtal:''' Aglim \"$1\" ulac-it. Ur tettuḍ ara belli isebtar \".css\" d \".js\" i txedmeḍ sseqdacen azwel i yesɛan isekkilen imecṭuḥen, s umedya: {{ns:user}}:Foo/vector.css akk d {{ns:user}}:Foo/Vector.css.",
        "updated": "(Yettubeddel)",
        "note": "'''Tamawt:'''",
        "previewnote": "'''Ttagi d azar-timeẓriwt kan, ibeddlen mazal ur ttusmektin ara!'''\n\n'''Cfut, ttagi d azar-timeẓriwt kan.'''\nIbeddlen mazal ur ttusmektin ara!",
        "prefs-files": "Ifayluwen",
        "prefs-custom-css": "CSS asagen",
        "prefs-custom-js": "JavaScript asagen",
-       "prefs-common-css-js": "JavaScript  d CSS azduklan i akkw lebsa :",
+       "prefs-common-config": "JavaScript  d CSS azduklan i akkw lebsa :",
        "prefs-reset-intro": "Tzemreḍ ad seqdeceḍ asebter agi iwakken ad erreḍ iɣewwaren inek/inem ar azalen n lexṣas n usmel.\nWagi ur yezmer ara ad yetwekkes.",
        "prefs-emailconfirm-label": "Aragag n tirawt :",
        "youremail": "E-mail *:",
index 0c29faf..14b3f39 100644 (file)
        "userjspreview": "'''Гу лъытэ, мыр япэ-еплъ къуэдейщ уи javascript-теплъэм, иджыри итхауэ щыткъым!'''",
        "sitecsspreview": "'''Зыщумгъэгъупшэ, мыр япэ-еплъ къуэдеуэ щытщ мы CSS-м.'''\n'''Иджыри итхауэ щыткъым!'''",
        "sitejspreview": "'''Зыщумгъэгъупшэ, мыр япэ-еплъ къуэдеуэ щытщ мы JavaScript-кодым.'''\n'''Иджыри итхауэ щыткъым!'''",
-       "userinvalidcssjstitle": "'''Гу лъытэ:''' лэжьыгъэр зтеухуа «$1»-р къэгъуэтауэ щыткъым. Зыщумыгъэгъупшэ, лэжьыгъэ напэкӀуэцӀ .css, .js-хэм цӀэ яӀэн хуэйхэ, сэтыр хьэрыф нэхъ яхэмыту, мыбым хуэду «{{ns:user}}:Згуэрэ/vector.css», щытын хуэйхэкъым мыбым хуэду: «{{ns:user}}:Згуэрэ/Vector.css».",
+       "userinvalidconfigtitle": "'''Гу лъытэ:''' лэжьыгъэр зтеухуа «$1»-р къэгъуэтауэ щыткъым. Зыщумыгъэгъупшэ, лэжьыгъэ напэкӀуэцӀ .css, .js-хэм цӀэ яӀэн хуэйхэ, сэтыр хьэрыф нэхъ яхэмыту, мыбым хуэду «{{ns:user}}:Згуэрэ/vector.css», щытын хуэйхэкъым мыбым хуэду: «{{ns:user}}:Згуэрэ/Vector.css».",
        "updated": "(КъэгъэщӀырыщӀащ)",
        "note": "'''Гулъытыгъуэ:'''",
        "previewnote": "'''Мыр япэ-еплъ къуэдей, тхылъыр иджыри итхакъым!'''",
index f412f1d..02fb2a5 100644 (file)
        "noarticletext-nopermission": "Na pele de hona thowa çino.\nTı şikina zerrê pelunê binu de [[Special:Search/{{PAGENAME}}|seba sernamê na pele cıfeteliyê]],\nya ki <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} cıkotena aide rê cıfeteliyê].</span>",
        "userpage-userdoesnotexist": "Hesabê karberi \"<nowiki>$1</nowiki>\" qeyd nêbiyo.\nKerem ke, tı ke wazena na pele vırazê/bıvurnê, qontrol ke.",
        "userpage-userdoesnotexist-view": "Hesabê karberi \"$1\" qeyd nêbiyo.",
-       "userinvalidcssjstitle": "'''Teme:''' Mewzuyê \"$1\" çino.\nDosyunê ebe namê .css u .js'y de herfa hurdiye bıgurêne, mesela hurêndia {{ns:user}}:Foo/Vector.css'i de {{ns:user}}:Foo/vector.css bınuse.",
+       "userinvalidconfigtitle": "'''Teme:''' Mewzuyê \"$1\" çino.\nDosyunê ebe namê .css u .js'y de herfa hurdiye bıgurêne, mesela hurêndia {{ns:user}}:Foo/Vector.css'i de {{ns:user}}:Foo/vector.css bınuse.",
        "updated": "(Bi rozane)",
        "note": "'''Not:'''",
        "previewnote": "Teme! ena teyna verqeyda.'''\nVurnayışê tu hama qeyd nıbiyo!",
index bdc3318..642ad37 100644 (file)
        "userjsyoucanpreview": "'''اقىل-كەڭەس:''' جاڭا JS فايلىن ساقتاۋ الدىندا «قاراپ شىعۋ» باتىرماسىن قولدانىپ سىناقتاڭىز.",
        "usercsspreview": "'''مىناۋ CSS ٴماتىنىن تەك قاراپ شىعۋ ەكەنىن ۇمىتپاڭىز, ول ٴالى ساقتالعان جوق!'''",
        "userjspreview": "'''مىناۋ JavaScript قاتىسۋشى باعدارلاماسىن تەكسەرۋ/قاراپ شىعۋ ەكەنىن ۇمىتپاڭىز, ول ٴالى ساقتالعان جوق!'''",
-       "userinvalidcssjstitle": "'''قۇلاقتاندىرۋ:''' وسى ارادا «$1» دەگەن ەش مانەر جوق.\nقاتىسۋشىنىڭ .css جانە .js فايل اتاۋى كىشى ارىپپپەن جازىلۋ ٴتىيىستى ەكەنىن ۇمىتپاڭىز, مىسالعا {{ns:user}}:Foo/vector.css دەگەندى {{ns:user}}:Foo/Vector.css دەگەنمەن سالىستىرىپ قاراڭىز.",
+       "userinvalidconfigtitle": "'''قۇلاقتاندىرۋ:''' وسى ارادا «$1» دەگەن ەش مانەر جوق.\nقاتىسۋشىنىڭ .css جانە .js فايل اتاۋى كىشى ارىپپپەن جازىلۋ ٴتىيىستى ەكەنىن ۇمىتپاڭىز, مىسالعا {{ns:user}}:Foo/vector.css دەگەندى {{ns:user}}:Foo/Vector.css دەگەنمەن سالىستىرىپ قاراڭىز.",
        "updated": "(جاڭارتىلعان)",
        "note": "'''اڭعارتپا:'''",
        "previewnote": "'''مىناۋ تەك قاراپ شىعۋ ەكەنىن ۇمىتپاڭىز;\nوزگەرىستەر ٴالى ساقتالعان جوق!'''",
index ca4071e..5c06c7c 100644 (file)
        "userjspreview": "<strong>Мынау JavaScript қатысушы бағдарламасын тынау/қарап шығу екенін ұмытпаңыз, ол әлі сақталған жоқ!</strong>",
        "sitecsspreview": "<strong>Мынау тек бұл CSS файлын қарап шығуыңыз екенін ұмытпаңыз, ол әлі сақталған жоқ!</strong>",
        "sitejspreview": "<strong>Мынау тек бұл JavaScript кодын алдын-ала қарап алу екенін ұмытпаңыз, ол әлі сақталған жоқ!</strong>",
-       "userinvalidcssjstitle": "</strong>Ескерту:</strong> Осы арада «$1» деген еш мәнер жоқ.\nҚалыпты .css және .js беттерінің атауына кіші әріп қолданыңыз, мысалы {{ns:user}}:Foo/vector.css дегенді {{ns:user}}:Foo/Vector.css дегенмен салыстырып қараңыз.",
+       "userinvalidconfigtitle": "</strong>Ескерту:</strong> Осы арада «$1» деген еш мәнер жоқ.\nҚалыпты .css және .js беттерінің атауына кіші әріп қолданыңыз, мысалы {{ns:user}}:Foo/vector.css дегенді {{ns:user}}:Foo/Vector.css дегенмен салыстырып қараңыз.",
        "updated": "(Жаңартылған)",
        "note": "'''Ескерту:'''",
        "previewnote": "<strong>Бұл тек қарап шығу екенін ұмытпаңыз.</strong> \nӨзгертулеріңіз әлі сақталған жоқ!",
        "prefs-files": "Файлдар",
        "prefs-custom-css": "CSS өзгертпелі",
        "prefs-custom-js": "JavaScript өзгертпелі",
-       "prefs-common-css-js": "Барлық skin-дер үшін CSS/JavaScript бөлісілді:",
+       "prefs-common-config": "Барлық skin-дер үшін CSS/JavaScript бөлісілді:",
        "prefs-reset-intro": "Сіз сайт әдепкі баптауларыңызды қайта орнату үшін осы бетті пайдалана аласыз.\nБұны болдырмау мүмкін емес.",
        "prefs-emailconfirm-label": "Е-поштаның расталуы:",
        "youremail": "Е-поштаңыз:",
index 6b8e784..393d138 100644 (file)
        "userjsyoucanpreview": "'''Aqıl-keñes:''' Jaña JS faýlın saqtaw aldında «Qarap şığw» batırmasın qoldanıp sınaqtañız.",
        "usercsspreview": "'''Mınaw CSS mätinin tek qarap şığw ekenin umıtpañız, ol äli saqtalğan joq!'''",
        "userjspreview": "'''Mınaw JavaScript qatıswşı bağdarlamasın tekserw/qarap şığw ekenin umıtpañız, ol äli saqtalğan joq!'''",
-       "userinvalidcssjstitle": "'''Qulaqtandırw:''' Osı arada «$1» degen eş mäner joq.\nQatıswşınıñ .css jäne .js faýl atawı kişi äripppen jazılw tïisti ekenin umıtpañız, mısalğa {{ns:user}}:Foo/vector.css degendi {{ns:user}}:Foo/Vector.css degenmen salıstırıp qarañız.",
+       "userinvalidconfigtitle": "'''Qulaqtandırw:''' Osı arada «$1» degen eş mäner joq.\nQatıswşınıñ .css jäne .js faýl atawı kişi äripppen jazılw tïisti ekenin umıtpañız, mısalğa {{ns:user}}:Foo/vector.css degendi {{ns:user}}:Foo/Vector.css degenmen salıstırıp qarañız.",
        "updated": "(Jañartılğan)",
        "note": "'''Añğartpa:'''",
        "previewnote": "'''Mınaw tek qarap şığw ekenin umıtpañız;\nözgerister äli saqtalğan joq!'''",
index f467d58..5837584 100644 (file)
        "userjspreview": "'កុំភ្លេចថាអ្នកគ្រាន់តែកំពុង ធ្វើតេស្ត/មើលមុន ទំព័រអ្នកប្រើប្រាស់  JavaScript របស់អ្នក។ វាមិនទាន់ត្រូវបានរក្សាទុកទេ!'''",
        "sitecsspreview": "\"កុំភ្លេចថាអ្នកកំពុងតែមើលមុន CSS នេះប៉ុណ្ណោះ។\"\n\"វាមិនទាន់ត្រូវបានរក្សាទុកទេ!\"",
        "sitejspreview": "\"កុំភ្លេចថាអ្នកកំពុងតែមើលមុន កូដJavaScript  នេះប៉ុណ្ណោះ។\"\n\"វាមិនទាន់ត្រូវបានរក្សាទុកទេ!\"",
-       "userinvalidcssjstitle": "'''ប្រយ័ត្ន៖''' គ្មានសំបក \"$1\"។ ចងចាំថា ទំព័រផ្ទាល់ខ្លួន .css និង .js ប្រើប្រាស់ ចំណងជើង ជាអក្សរតូច, ឧទាហរណ៍  {{ns:user}}:Foo/vector.css ត្រឹមត្រូវ, រីឯ {{ns:user}}:Foo/Vector.css មិនត្រឹមត្រូវ។",
+       "userinvalidconfigtitle": "'''ប្រយ័ត្ន៖''' គ្មានសំបក \"$1\"។ ចងចាំថា ទំព័រផ្ទាល់ខ្លួន .css និង .js ប្រើប្រាស់ ចំណងជើង ជាអក្សរតូច, ឧទាហរណ៍  {{ns:user}}:Foo/vector.css ត្រឹមត្រូវ, រីឯ {{ns:user}}:Foo/Vector.css មិនត្រឹមត្រូវ។",
        "updated": "(បានបន្ទាន់សម័យ)",
        "note": "'''ចំណាំ៖'''",
        "previewnote": "'''សូមចាំថានេះគ្រាន់តែជា​ការបង្ហាញការមើលជាមុនប៉ុណ្ណោះ។ បន្លាស់ប្ដូរ​របស់អ្នកមិនទាន់បាន​រក្សាទុកទេ!'''",
        "prefs-files": "ឯកសារ",
        "prefs-custom-css": "CSSកម្លាយ",
        "prefs-custom-js": "JavaScriptកម្លាយ",
-       "prefs-common-css-js": "CSS/JavaScriptរួមសម្រាប់សំបកទាំងអស់៖",
+       "prefs-common-config": "CSS/JavaScriptរួមសម្រាប់សំបកទាំងអស់៖",
        "prefs-reset-intro": "អ្នក​អាច​ប្រើ​ទំព័រ​នេះ​ដើម្បី​កំណត់​ចំណង់ចំណូល​ចិត្ត​របស់​អ្នក​ដូច​លំនាំ​ដើម​ឡើង​វិញ​។\nសកម្មភាព​នេះ​មិន​អាច​ឈប់ធ្វើ​ឡើង​វិញ​បាន​ទេ​។",
        "prefs-emailconfirm-label": "បញ្ជាក់ទទួលស្គាល់អ៊ីមែល៖",
        "youremail": "អ៊ីមែល៖",
index ecf9ce8..7812d03 100644 (file)
        "userjspreview": "'''사용자 자바스크립트 미리 보기입니다.'''\n'''아직 저장하지 않았습니다!'''",
        "sitecsspreview": "'''이 CSS의 미리 보기입니다.'''\n'''아직 저장하지 않았습니다!'''",
        "sitejspreview": "'''이 자바스크립트 코드의 미리 보기입니다.'''\n'''아직 저장하지 않았습니다!'''",
-       "userinvalidcssjstitle": "<strong>경고:</strong> \"$1\" 스킨은 없습니다.\n.css와 .js 문서의 제목은 {{ns:user}}:Foo/vector.css 처럼 소문자로 써야 합니다. {{ns:user}}:Foo/Vector.css 와 같이 대문자로 쓸 경우 작동하지 않습니다.",
+       "userinvalidconfigtitle": "<strong>경고:</strong> \"$1\" 스킨은 없습니다.\n.css와 .js 문서의 제목은 {{ns:user}}:Foo/vector.css 처럼 소문자로 써야 합니다. {{ns:user}}:Foo/Vector.css 와 같이 대문자로 쓸 경우 작동하지 않습니다.",
        "updated": "(바뀜)",
        "note": "<strong>참고:</strong>",
        "previewnote": "'''이 화면은 미리 보기입니다.'''\n편집한 내용은 아직 저장하지 않았습니다!",
        "prefs-files": "파일",
        "prefs-custom-css": "사용자 CSS",
        "prefs-custom-js": "사용자 자바스크립트",
-       "prefs-common-css-js": "모든 스킨에 공유된 CSS/자바스크립트:",
+       "prefs-common-config": "모든 스킨에 공유된 CSS/자바스크립트:",
        "prefs-reset-intro": "이 페이지를 사용해 사이트 기본값으로 환경 설정을 재설정할 수 있습니다.\n이는 되돌릴 수 없습니다.",
        "prefs-emailconfirm-label": "이메일 인증:",
        "youremail": "이메일:",
        "thumbnail_dest_directory": "새 목적 디렉터리를 만들 수 없습니다.",
        "thumbnail_image-type": "그림 형식이 지원되지 않습니다",
        "thumbnail_gd-library": "GD 라이브러리 설정이 잘못되었습니다: $1 함수를 찾을 수 없습니다.",
+       "thumbnail_image-size-zero": "그림 파일 크기가 0인 것으로 보입니다.",
        "thumbnail_image-missing": "파일을 찾을 수 없습니다: $1",
        "thumbnail_image-failure-limit": "여기에 이 섬네일을 렌더하는 데 최근에 너무 많이 실패한 시도($1 이상)가 있습니다.\n나중에 다시 시도하세요.",
        "import": "문서 가져오기",
index 89e9c6a..0ad036c 100644 (file)
        "userjspreview": "'''Эсде тутугъуз, бу къуру ал къарауду, javascript файлыгъыз алкъын сакъланмагъанды!'''",
        "sitecsspreview": "'''Эслегиз, бу CSS-ни къуру ал къараууду.'''\n '''Ол алкъын сакъланмагъанды!'''",
        "sitejspreview": "'''Эслегиз, бу JavaScript-кодну къуру ал къараууду.'''\n '''Ол алкъын сакъланмагъанды!'''",
-       "userinvalidcssjstitle": "'''Эс бёлюгюз:''' «$1» атлы тема джокъду. Эсде тутугъуз, .css эм .js бетле атлары ажымсыз къуру гитче харифледен болургъа керекди, сёз ючюн: {{ns:user}}:Foo/vector.css, былай  {{ns:user}}:Foo/Vector.css тюйюл!",
+       "userinvalidconfigtitle": "'''Эс бёлюгюз:''' «$1» атлы тема джокъду. Эсде тутугъуз, .css эм .js бетле атлары ажымсыз къуру гитче харифледен болургъа керекди, сёз ючюн: {{ns:user}}:Foo/vector.css, былай  {{ns:user}}:Foo/Vector.css тюйюл!",
        "updated": "(Джангыртылды)",
        "note": "'''Белги:'''",
        "previewnote": "'''Бу къуру ал къарауду.'''\nСиз этген тюрлениуле алкъын сакъланмагъандыла!",
        "prefs-files": "Файлла",
        "prefs-custom-css": "Энчи CSS",
        "prefs-custom-js": "Энчи JS",
-       "prefs-common-css-js": "Бир CSS/JS-ле, халны бары темаларына да:",
+       "prefs-common-config": "Бир CSS/JS-ле, халны бары темаларына да:",
        "prefs-reset-intro": "Бу бетни джарашдырыуларыгъызны тынгылыау бла салыннган джарашдырыулагъа кёчюрюрге хайырланаллыкъсыз.\nБу ишлемни къабыл этсегиз, ызына къайтараллыкъ тюлсюз.",
        "prefs-emailconfirm-label": "Электрон почтаны бегитиу:",
        "youremail": "Электрон почта:",
index a2df772..5c91541 100644 (file)
        "userjspreview": "<strong>Opjepass:</strong> Do bes heh nor am Usprobeere, wat Ding\nMetmaacher_Java_Skripp mäht, et es noch nit jesechert!\n\n<strong>Opjepass:</strong> Noh dem Avspeichere moß de Dingem Brauser noch singe Cache fottschmiiße.\nDat jeit je noh Bauser met ongerscheidleje Knöpp —\nbeim '''Mozilla''' un em '''Firefox''': ''Strg-Shift-R'' —\nem '''Internet Explorer''': ''Strg-F5'' —\nför der '''Opera''': ''F5'' —\nmem '''Safari''': ''Cmd-Shift-R'' —\nun em '''Konqueror''': ''F5'' —\net ess en bunte Welt!",
        "sitecsspreview": "'''Opjepass:''' Do bes heh nor am Usprobeere, wat Ding CSS mäht,\net es noch nit jesechert!",
        "sitejspreview": "<strong>Opjepass:</strong> Do bes heh nor am Usprobeere, wat Ding\nJava_Skripp mäht, et es noch nit jesechert!",
-       "userinvalidcssjstitle": "<strong>Opjepass:</strong> Et jitt keij Ußsinn met dämm Nahme: „<strong>$1</strong>“ -\ndängk drahn, dat ene Metmaacher eije Datteije för et Ußsinn han kann, un dat di met kleijne Bohchstahve\naanfange dun, alsu etwa: „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">{{ns:user}}:Name/vector.css</code>“, un \n „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">{{ns:user}}:Name/vector.js</code>“ heijße.",
+       "userinvalidconfigtitle": "<strong>Opjepass:</strong> Et jitt keij Ußsinn met dämm Nahme: „<strong>$1</strong>“ -\ndängk drahn, dat ene Metmaacher eije Datteije för et Ußsinn han kann, un dat di met kleijne Bohchstahve\naanfange dun, alsu etwa: „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">{{ns:user}}:Name/vector.css</code>“, un \n „<code lang=\"en\" xml:lang=\"en\" dir=\"ltr\">{{ns:user}}:Name/vector.js</code>“ heijße.",
        "updated": "(Aanjepack)",
        "note": "'''Opjepass:'''",
        "previewnote": "<strong>Heh kütt blohß en Aanseesch vöraff — Ding Änderonge sin noch nidd em Wikki faßjehallde!</strong>",
        "prefs-files": "Dateie",
        "prefs-custom-css": "Selfsjemaat <i lang=\"en\">Cascading Style Sheet</i>",
        "prefs-custom-js": "Selfsjemaat JavaSkripp",
-       "prefs-common-css-js": "Gemeinsam CSS un JavaSkrepp för all de Bovverfläshe:",
+       "prefs-common-config": "Gemeinsam CSS un JavaSkrepp för all de Bovverfläshe:",
        "prefs-reset-intro": "Op dä Sigg kanns De Ding Ennschtällong op dämm Wikki singe Schandatt setze lohße. Ävver Opjepaß: Do jidd et keine „Retuhr“-Knopp för!",
        "prefs-emailconfirm-label": "Beshtätejung övver <i lang=\"en\">e-mail</i>:",
        "youremail": "E-Mail *",
index 6ef7b8e..1554705 100644 (file)
        "delete_and_move_reason": "Jêbir ji bo navguherandinê",
        "immobile-source-page": "Navê vê rûpelê nikare were guherandin.",
        "move-leave-redirect": "Beralîkirinekê bihêle",
-       "export": "Rûpelan eksport bike",
+       "export": "Rûpelan derxîne",
        "export-addcat": "Zêde bike",
        "export-addns": "Zêde bike",
        "export-download": "Weka dosyeyê qeyd bike",
index dc85920..3298422 100644 (file)
@@ -61,8 +61,9 @@
        "category-media-header": "\"$1\" категориядагъы сапламлар",
        "category-empty": "\"Бу категория буссагьат бош.\"",
        "hidden-categories": "{{PLURAL:$1|Яшырылгъан категория|Яшырылгъан категориялар}}‎",
-       "category-subcat-count": "{{PLURAL:$2|Ð\91Ñ\83 ÐºÐ°Ñ\82егоÑ\80иÑ\8fда Ñ\8fнгÑ\8bз Ð³ÐµÐ»ÐµÐ³ÐµÐ½ Ñ\82Ñ\8eп ÐºÐ°Ñ\82егоÑ\80иÑ\8fÑ\81Ñ\8b Ð±Ð°Ñ\80.|Ð\91Ñ\83 ÐºÐ°Ñ\82егоÑ\80иÑ\8fда Ñ\8fнгÑ\8bз Ð³ÐµÐ»ÐµÐ³ÐµÐ½ $2 Ñ\82Ñ\8eп ÐºÐ°Ñ\82егоÑ\80Ñ\8fдан {{PLURAL:$1|Ñ\82Ñ\8eп ÐºÐ°Ñ\82егоÑ\80иÑ\8f}} Ð³Ñ\91Ñ\80Ñ\81еÑ\82илген.}}",
+       "category-subcat-count": "{{PLURAL:$2|Ð\91Ñ\83 ÐºÐ°Ñ\82егоÑ\80иÑ\8fда Ñ\82ек Ñ\81онггÑ\8aÑ\83 Ð³ÐµÐ»ÐµÐ³ÐµÐ½ Ñ\82Ñ\8eпкаÑ\82егоÑ\80иÑ\8fÑ\81Ñ\8b Ð±Ð°Ñ\80.|Ð\91Ñ\83 ÐºÐ°Ñ\82егоÑ\80иÑ\8fда {{PLURAL:$1|Ñ\82Ñ\8eпкаÑ\82егоÑ\80иÑ\8f|$1 Ñ\82Ñ\8eпкаÑ\82егоÑ\80иÑ\8fлаÑ\80}} Ð±Ð°Ñ\80, Ð¶Ð°Ð¼Ð´Ð° $2 Ñ\82Ñ\8eпкаÑ\82егоÑ\80иÑ\8fдан.}}",
        "category-article-count": "{{PLURAL:$2|Бу категорияда янгыз гелеген бир сагьифа бар.|Бу категорияда бар $2 сагьифаны {{PLURAL:$1|сагьифасы}} гёрсетилген.}}",
+       "category-file-count": "{{PLURAL:$2|Бу категорияда бир тек сонггъу гелеген сапламы бар.|Сонггъу гелеген {{PLURAL:$1|саплам|$1 сапламлар}}{{PLURAL:$2|категориядадыр|категориядадырлар}}}}.",
        "listingcontinuesabbrev": "давам",
        "noindex-category": "Индексленмейген сагьифалар",
        "broken-file-category": "Ишлеймейген саплам байланывлары булан сагьифалар",
        "viewsource-title": "$1 сагьифаны аслу текстине къарамакъ",
        "viewsourcetext": "Бу сагьифаны аслу кодуна къарап ва ону кёплеп боласан.",
        "userlogin-yourname": "Къоллавчу аты",
-       "userlogin-yourname-ph": "Гьисабынг атын бер",
+       "userlogin-yourname-ph": "Гьисабынгны атын бер",
        "userlogin-yourpassword": "Чечил",
        "userlogin-yourpassword-ph": "Чечилингни бер",
        "createacct-yourpassword-ph": "Чечилингни бер",
        "createaccount": "Янгы бет этмек",
        "userlogin-resetpassword-link": "Чечилингни унутгъанмысан?",
        "userlogin-helplink2": "Гириш саялы кёмек",
-       "createacct-emailoptional": "ЭлекÑ\82Ñ\80он Ð¿Ð¾Ñ\87нÑ\83 ÐµÑ\80леÑ\88ими (гÑ\8cажаÑ\82 Ñ\91кÑ\8a)",
+       "createacct-emailoptional": "ЭлекÑ\82Ñ\80он Ð¿Ð¾Ñ\87нÑ\83 ÐµÑ\80леÑ\88ими (гÑ\91нгÑ\8eллÑ\8e)",
        "createacct-email-ph": "Электрон почну ерлешимин бер",
        "createacct-submit": "Бетинг яса",
        "createacct-benefit-heading": "{{SITENAME}} сени йимик адамланы ортакъ загьматы.",
        "passwordreset": "Чечилни янгыдан бегетмек",
        "bold_sample": "Къалын матын",
        "bold_tip": "Къалын матын",
-       "italic_sample": "Авункъу текст",
-       "italic_tip": "Авункъу текст",
+       "italic_sample": "Авункъу матын",
+       "italic_tip": "Авункъу матын",
        "link_sample": "Байланывну аты",
        "link_tip": "Ич байланыв",
        "extlink_sample": "http://www.example.com байланывну аты",
        "extlink_tip": "Тыш байланыв (бу префиксни http:// эсде сакъла)",
-       "headline_sample": "Язывбашны тексти",
+       "headline_sample": "Язывбашны матыны",
        "headline_tip": "2 даражаны язывбашы",
        "nowiki_sample": "Форматланмагъа гере тюгюл матынны бери сал",
        "nowiki_tip": "Вики форматлавну сан этме",
        "preview": "Ингкъарав",
        "showpreview": "Ингкъарав",
        "showdiff": "Тюзлевлени гёрсетмек",
+       "anoneditwarning": "<strong>Тергев:</strong> Сен гириш этмединг. Тюзлевлер этсенг сени IP адресинг публикли гёрюнер. Эгер <strong>[$1 гириш]</strong> яда <strong>[$2 къайыт]</strong> этсенг, тюзлевлеринг сени ортакъчы аты булан гьисап этилер, оьзге пайдалардан да къайры.",
+       "blockedtext": "Сени къоллавчу атынг яда IP адресинг [[m:Special:MyLanguage/Global blocks|бары викилерден]] къамалгъан эди.''\n\nКъамав этген $1 ($2).\nСебеп берилген: ''$3'.\n\n* Къамав башлады: $4\n* Къамав бите: $5\n* Къамавну мурады: $7\n\n$1 булан яда къайсы оьзге админ булан къатнап къамавну гьакъында сёйлешмеге боласан.\nТергеп къой, сени [[Special:Preferences|энчили кюйлемлерингде]] e-mail адресинг тюз бермединг буса яда герти этмединг буса, яда къамав шартлагъа мактуп язывуну къадагъа гире буса, \"къоллавчугъа мактуп\" функцияны къоллап болмассан.\nСени IP адресинг — $3, къамавну идентификатору — $5. Тилев, бу маълюматланы бары талапларынга гийир.",
        "loginreqlink": "гирмек",
        "noarticletext": "Бу сагьифа гьали де матынсыз. Сен башгъа сагьифаларда [[Special:Search/{{PAGENAME}}|булай атын эсгеривлерини излемеге]]  боласан, <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тийишли гюнделик язывланы тапмагъа] яда '''[{{fullurl:{{FULLPAGENAME}}|action=edit}} булай аты булан сагьифа яратмагъа боласан]'''</span>.",
        "noarticletext-nopermission": "Бу сагьифа гьали де текстсиз. Сен башгъа сагьифаларда [[Special:Search/{{PAGENAME}}|булай атын эсгеривлерини излемеге]], яда <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} тийишли гюнделиклени тапмагъа боласан]. </span> Тек бу сагьифаны яратмагъа ихтиярынг ёкъ.",
        "permissionserrors": "Гириш ихтиярланы хатасы",
        "permissionserrorstext-withaction": "Бугъар $2 гелеген {{PLURAL:$1|себепге|себеплеге}} гёре ихтиярынг ёкъ:",
        "recreate-moveddeleted-warn": "<strong>Тергев бер: Алдын тайдырылгъан сагьифаны ярашдырма айланасан.</strong>\n\nБашлап тергеп къара, гертиден де тарыкъмы экен ол сагьифаны ярашдырмагъа.\nТайдырыв ва атын алышдырыв гюнделиги тюпде берилген:",
+       "moveddeleted-notice": "Бу сагьифа тайдырылгъан эди.\nБу сагьифаны тайдырыв, къорув ва атянгыртыв гюнделиги маълюмат саялы тюпде гёрсетилген.",
        "content-model-wikitext": "викиматын",
        "undo-failure": "Аралыкъ алышынывланы къыйышывсызлгъы учун тюзлев гери алынмай.",
        "viewpagelogs": "Бу сагьифа учун гюнделиклени гёрсетмек",
        "prevn": "алдагъы {{PLURAL:$1|$1}}",
        "nextn": "сонггъу {{PLURAL:$1|$1}}",
        "prevn-title": "Алдагъы $1 {{PLURAL:$1|гьасил}}",
-       "nextn-title": "Гелеген $1 {{PLURAL:$1|гьасил}}",
+       "nextn-title": "Гелеген $1 {{PLURAL:$1|гьасил|гьасиллер}}",
        "shown-title": "Айры бир сагьифада $1 {{PLURAL:$1|языв|язывланы}} гёрсетмек",
        "viewprevnext": "($1 {{int:pipe-separator}} $2) ($3) къарамакъ",
        "searchmenu-exists": "<strong>Бу викиде \"[[:$1]]\" деп аты булан сагьифа бар.</strong> {{PLURAL:$2|0=|Дагъы да табылгъан натижалагъа да къара.}}",
        "search-file-match": "(сапламны ичделигине рас геле)",
        "search-suggest": "Буну ойлаймысан экен: $1",
        "searchall": "бары да",
+       "search-showingresults": "{{PLURAL:$4|<strong>$1</strong> натижа <strong>$3</strong> натижалардан|<strong>$1 – $2</strong> натижа <strong>$3</strong> натижалардан}}",
        "search-nonefound": "Бу талапгъа тийишли натижалар табулмады.",
        "mypreferences": "Кюйлемлер",
        "group-bot": "Ботлар",
        "recentchanges-label-plusminus": "Сагьифаны гёлеми шунчакъы байт булан алышынгъан",
        "recentchanges-legend-heading": "<strong>Уйдурма:</strong>",
        "recentchanges-legend-newpage": "{{int:recentchanges-label-newpage}} (шунда да  [[Special:NewPages|янгы сагьифаланы сиягьына къарагъыз]])",
+       "rcnotefrom": "Тюбюндеги — <strong>$3, $4</strong> гюнден берли {{PLURAL:$5|тюзлевюдюр|тюзлевлеридир}}.",
        "rclistfrom": "$2, $3 тюзлевлерден башлап гёрсетмек",
        "rcshowhideminor": "$1 увакъ тюзлевлер",
        "rcshowhideminor-show": "Гёрсетмек",
        "recentchangeslinked-feed": "Байлавлу тюзлевлер",
        "recentchangeslinked-toolbox": "Байлавлу тюзлевлер",
        "recentchangeslinked-title": "\"$1\" сагьифагъа байлавлу тюзлевлер",
-       "recentchangeslinked-summary": "Бу сагьифагъа байлавлу тюзлевлери булан сагьифаланы яда о сагьифа байлавлуланы гёрмек учун атын яз. (Категорияны ортакъчаларын гёрмек учун Category:категорияны атын яз). Сени [[Special:Watchlist|гьызарлав тизменгдеги]] алышынывлар <strong>къалын</strong> гьарплылар.",
+       "recentchangeslinked-summary": "Бу сагьифагъа байлавлу тюзлевлери булан сагьифаланы, яда о сагьифадан байлавлуланы гёрмек учун атын яз. (Категорияны ортакъчаларын гёрмек учун Category:Категорияны атын яз). Сени [[Special:Watchlist|Гьызарлав тизменгдеги]] алышынывлар <strong>къалын</strong> гьарплылар.",
        "recentchangeslinked-page": "Сагьифаны аты:",
        "recentchangeslinked-to": "Къайта, бу сагьифагъа байлавлу сагьифаланы алышынывларын гёрсетмек",
        "upload": "Сапламны юклемек",
        "imagelinks": "Сапламны къоллаву",
        "linkstoimage": "Шундан сонггъу {{PLURAL:$1|сагьифа|$1 сагьифалар}} бу сапламгъа байлангъан:",
        "linkstoimage-more": "$1 {{PLURAL:$1|сагьифадан}} артыкъ иг сапламгъа байланывлу.\nБу тизме янгыз бу сапламгъа {{PLURAL:$1|first page link|биринчи $1 байланывлу сагьифаны}} гёрсете.\n[[Special:WhatLinksHere/$2|Толу тизме де]] бар.",
-       "nolinkstoimage": "Бу сапламгъа байлавлу сагьифалар ёкъ",
+       "nolinkstoimage": "Бу сапламгъа байлавлу сагьифалар ёкъдур",
        "linkstoimage-redirect": "$1 (саплам ёллав) $2",
        "sharedupload-desc-here": "Бу саплам $1 проектдендир, ва башгъасында да къолланма бола. Ону [$2 тасвир сагьифасындан] маълюматы тюпде бериле.‎",
        "filepage-nofile": "Булай аты булан саплам ёкъдур.",
        "unwatch": "Гери гьызарламакъ",
        "watchlist-details": "{{PLURAL:$1|$1 сагьифа}} сени тергев тизменгде (пикирлешив сагьифаланы санап).",
        "wlheader-showupdated": "Сени ахырынчы геливюнгден сонг алышынгъан сагьифалар <strong>къалын</strong> шрифт булан гёрсетилген.",
+       "wlnote": "Тюбюндеги — {{PLURAL:$1|ахырынчы тюзлевюдюр|<strong>$1</strong> ахырынчы тюзлевлеридир}} алдагъы {{PLURAL:$2|сагьат аралыкъда|<strong>$2</strong> сагьат аралыкъда}} этилген, $3, $4 тархындан.",
        "wlshowlast": "Артдагъы $1 сагьат $2 гюннюкин гёрсетмек",
        "watchlist-options": "Тергев тизмени кюйлевлери",
        "enotif_reset": "Бары да сагьифаланы къаралгъандай белгилемек",
        "restriction-move": "Атын алышдырмакъ",
        "namespace": "Атлар аралыгъы:",
        "invert": "Сайлангъанын айландырмакъ",
+       "tooltip-invert": "Сайлангъан ат аралыкъдагъы (ва белгиленген буса огъар байланышлы ат аралыкъдагъы да) тюзлевлерин яшырмакъ учун бу белгини салып къой.",
        "namespace_association": "Байланышлы атлар аралыгъы",
        "tooltip-namespace_association": "Сайлангъан атысакъламагъа байлавлу атсакъламаны, пикирлешивлени къошмакъ учун бу белгини де сал.",
        "blanknamespace": "(Аслу)",
        "contributions": "{{GENDER:$1|Къоллавчуну}} къошуму",
        "contributions-title": "Ортакъчыны ярдымы $1",
-       "mycontris": "Къошуму",
-       "anoncontribs": "Къошуму",
+       "mycontris": "Къошум",
+       "anoncontribs": "Къошум",
        "contribsub2": "Ярдым {{GENDER:$3|$1}} ($2)",
        "nocontribs": "Берилген усуллагъа къыйышывлу алышынывлар табылмагъан.",
        "uctop": "(гьалиги)",
        "show-big-image-other": "Башгъа {{PLURAL:$2|айырым|айырымлар}}: $1.",
        "show-big-image-size": "$1 × $2 пиксел",
        "metadata": "Мета маълюматлар",
+       "metadata-help": "Бу саплап къошум маълюмат сакълай, балики, санавлу камера яда сканнер табакъ яратылгъан яда санавлашгъан.\nЭгер саплам аслу гьалдан тюрленген буса, маълюматлардан бирлери алышынгъан тюрюн толу кюйде къыйышмайлы болмагъа ярай.",
+       "metadata-fields": "Эгер метамаълюмат табел дагъылса, бу мактупда тизилген суратланы метамаълюмат аралыкълары сурат сагьифаны дисплейине гийирилер.\nКъалгъандал кюрчю кюйлемлеге гёре яшырыларлар.\n* make\n* model\n* datetimeoriginal\n* exposuretime\n* fnumber\n* isospeedratings\n* focallength\n* artist\n* copyright\n* imagedescription\n* gpslatitude\n* gpslongitude\n* gpsaltitude",
        "exif-orientation": "Онгарылым",
        "exif-xresolution": "Гёнделен айырым",
        "exif-yresolution": "Тик айырым",
index 6f98e93..3913c06 100644 (file)
        "userjspreview": "'''Denkt drun datt Dir Äre Javascript nëmmen test.'''\n'''En ass nach net gespäichert!'''",
        "sitecsspreview": "'''Denkt drun datt Dir dësen CSS just kuckt.\nE gouf nach net gespäichert!'''",
        "sitejspreview": "'''Denkt drun datt Dir dëse JavaScript-Code just kuckt.\nE gouf nach net gespäichert!'''",
-       "userinvalidcssjstitle": "'''Opgepasst:''' Et gëtt keen Ausgesinn (skin) \"$1\".\nDenkt drun datt eegen .css an .js Säiten e kleng geschriwwenen Titel benotzen, z. Bsp. {{ns:user}}:Foo/vector.css am Géigesaz zu {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Opgepasst:''' Et gëtt keen Ausgesinn (skin) \"$1\".\nDenkt drun datt eegen .css an .js Säiten e kleng geschriwwenen Titel benotzen, z. Bsp. {{ns:user}}:Foo/vector.css am Géigesaz zu {{ns:user}}:Foo/Vector.css.",
        "updated": "(Geännert)",
        "note": "'''Notiz:'''",
        "previewnote": "'''Denkt drun datt dëst nëmmen eng net gespäichert Versioun ass.'''\nÄr Ännerunge sinn nach net gespäichert!",
        "prefs-files": "Fichieren",
        "prefs-custom-css": "Benotzerdefinéierten CSS",
        "prefs-custom-js": "Benotzerdefinéierte JS",
-       "prefs-common-css-js": "Gemeinsam CSS/JS fir all Ausgesinn (skins):",
+       "prefs-common-config": "Gemeinsam CSS/JS fir all Ausgesinn (skins):",
        "prefs-reset-intro": "Dir kënnt dës Säit benotze fir Är Astellungen zréck op d'Standard-Astllungen ze setzen.\nDëst kann net réckgängeg gemaach ginn.",
        "prefs-emailconfirm-label": "E-Mail Confirmatioun:",
        "youremail": "E-Mail-Adress:",
        "thumbnail_dest_directory": "Den Zilepertoire konnt net ugeluecht ginn.",
        "thumbnail_image-type": "Bildtyp gëtt net ënnerstëtzt",
        "thumbnail_gd-library": "D'Konfiguratioun vun der GD-Bibliothéik (GD library) ass net komplett: D'Funktioun $1 feelt",
+       "thumbnail_image-size-zero": "D'Gréisst vum Fichier vum Bild schéngt null ze sinn.",
        "thumbnail_image-missing": "De Fichier schengt ze feelen: $1",
        "import": "Säiten importéieren",
        "importinterwiki": "Vun enger anerer Wiki importéieren",
index 4f57acb..daeb9d6 100644 (file)
        "userjspreview": "<strong>Recorda ce tu regarda mera un proba/previde de tua JavaScript de usor.\nLo es ancora no fisada!</strong>",
        "sitecsspreview": "<strong>Recorda ce tu regarda mera un previde de esta CSS.\nLo es ancora no fisada!</strong>",
        "sitejspreview": "<strong>Recorda ce tu regarda mera un previde de esta codigo JavaScript.\nLo es ancora no fisada!</strong>",
-       "userinvalidcssjstitle": "<strong>Avisa:</strong> No pel \"$1\" esiste.\nPajes .css e .js personal usa un titulo con leteras minor, pe {{ns:user}}:Foo/vector.css en loca de {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Avisa:</strong> No pel \"$1\" esiste.\nPajes .css e .js personal usa un titulo con leteras minor, pe {{ns:user}}:Foo/vector.css en loca de {{ns:user}}:Foo/Vector.css.",
        "updated": "(Cambiada)",
        "note": "<strong>Nota:</strong>",
        "previewnote": "<strong>Recorda ce esta es mera un previde.</strong>\nTua cambias es ancora no fisada!",
        "prefs-files": "Fixes",
        "prefs-custom-css": "CSS personal",
        "prefs-custom-js": "JavaScript personal",
-       "prefs-common-css-js": "CSS/JavaScript comun per tota peles:",
+       "prefs-common-config": "CSS/JavaScript comun per tota peles:",
        "prefs-reset-intro": "Tu pote usa esta paje per reinisia tua preferes a la inisiales per esta pajeria.\nTu no pote desfa esta.",
        "prefs-emailconfirm-label": "Confirma de eposta:",
        "youremail": "Eposta:",
index 243159c..d8f54b8 100644 (file)
        "userjspreview": "'''Jukira nti JavaScript gw'otegese omugezesamubugezesa oba omulozakobulozi.'''<br />\n'''Tannakazibwa.'''",
        "sitecsspreview": "'''Jukira nti CSS ono omulozakobulozi.'''<br />\n'''Tannakazibwa.'''",
        "sitejspreview": "'''Jukira nti JavaScript ono omulozakobulozi.'''<br />\n'''Tannakazibwa.'''",
-       "userinvalidcssjstitle": "'''Kulabula:''' Tewali endabika eyitibwa \"$1\".<br />\nEmpapula eza .css ne .js bamemba ze b'ekoledde, amannya ga zo ennukuta za mu<br />\nzonna ziteekwa okuba ntono, okugeza ''{{ns:user}}:Foo/vector.css'' so ssi ''{{ns:user}}:Foo/Vector.css''.",
+       "userinvalidconfigtitle": "'''Kulabula:''' Tewali endabika eyitibwa \"$1\".<br />\nEmpapula eza .css ne .js bamemba ze b'ekoledde, amannya ga zo ennukuta za mu<br />\nzonna ziteekwa okuba ntono, okugeza ''{{ns:user}}:Foo/vector.css'' so ssi ''{{ns:user}}:Foo/Vector.css''.",
        "updated": "(Ebituukanisidwa)",
        "note": "'''Okunnyonyola:'''",
        "previewnote": "'''Kuno kugezaamubugeza; by'okoze tebinnakazibwa!'''",
index 5797798..85e6a98 100644 (file)
        "userjspreview": "<strong>'''Lèt op: doe tes noe dien persuunlik JavaScript.\nDe pagina is neet opgesjlage!</strong>",
        "sitecsspreview": "'''Dit is allein 'n veurvertuin van de CSS.'''\n'''Deze is nog neet opgesjlage!'''",
        "sitejspreview": "<strong>Dit is allein 'n veurvertuin van de JavaScriptcode.\nDe code is nog neet opgesjlage!</strong>",
-       "userinvalidcssjstitle": "'''Waarsjoewing:''' d'r is gein skin \"$1\". \nLèt op: dien eige .css- en .js-pagina's beginne mèt  'ne klein lètter, beveurbeeld {{ns:user}}:Naam/vector.css in plaats van {{ns:user}}:Naam/Vector.css.",
+       "userinvalidconfigtitle": "'''Waarsjoewing:''' d'r is gein skin \"$1\". \nLèt op: dien eige .css- en .js-pagina's beginne mèt  'ne klein lètter, beveurbeeld {{ns:user}}:Naam/vector.css in plaats van {{ns:user}}:Naam/Vector.css.",
        "updated": "(Biegewèrk)",
        "note": "'''Opmirking:'''",
        "previewnote": "<strong>Lèt op: dit is 'n controlepazjena; dien teks is nog neet opgesjlage!</strong>",
        "prefs-files": "Bestenj",
        "prefs-custom-css": "Persoonlijke CSS",
        "prefs-custom-js": "Persoonlijke JS",
-       "prefs-common-css-js": "Gedeilde CSS/JS veur eder vormgaeving:",
+       "prefs-common-config": "Gedeilde CSS/JS veur eder vormgaeving:",
        "prefs-reset-intro": "Gebroek dees functie om dien veurkäöre te herstelle nao de standaardinstellinge.\nDees hanjeling kin neet ongedaon gemaak waere.",
        "prefs-emailconfirm-label": "E-mailbevestiging:",
        "youremail": "Dien e-mailadres",
index 58978b9..0b7b526 100644 (file)
        "userlogin-yourname-ph": "Inserisci o teu nómme uténte",
        "createacct-another-username-ph": "Scrivi o teu nomme utente",
        "yourpassword": "Pòula segretta:",
-       "userlogin-yourpassword": "Inserisci a teu ciâve",
+       "userlogin-yourpassword": "Ciâve",
        "userlogin-yourpassword-ph": "Scrivi a tu poula segretta.",
        "createacct-yourpassword-ph": "Scrivi 'na poula segretta.",
        "yourpasswordagain": "Riscrivi a pòula segrétta:",
        "userjspreview": "'''Questa a l'è solo un'anteprimma do proppio  JavaScript personâ. E modiffiche no son ancon stæte sarvæ!'''",
        "sitecsspreview": "'''Questa a l'è solo un'anteprimma do CSS. E modiffiche no son ancon stæte sarvæ!'''",
        "sitejspreview": "'''Questa a l'è solo un'anteprimma pe provâ o JavaScript; e modiffiche no son ancon stæte sarvæ!'''",
-       "userinvalidcssjstitle": "<strong>Attension:</strong> no existe arcun tema con nomme \"$1\". E paggine pe-i .css e .js personalizzæ g'han o tittolo minuscolo, presempio {{ns:user}}:Esempio/vector.css e no {{ns:user}}:Esempio/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Attension:</strong> no existe arcun tema con nomme \"$1\". E paggine pe-i .css e .js personalizzæ g'han o tittolo minuscolo, presempio {{ns:user}}:Esempio/vector.css e no {{ns:user}}:Esempio/Vector.css.",
        "updated": "(Aggiornòu)",
        "note": "'''Notta:'''",
        "previewnote": "'''Questa chì a l'è solo 'n'anteprimma; i cangiamenti no son ancon stæti sarvæ!'''",
        "prefs-files": "File",
        "prefs-custom-css": "CSS personalizzou",
        "prefs-custom-js": "JavaScript personalizzou",
-       "prefs-common-css-js": "CSS/JavaScript condiviso pe tutte e pelle:",
+       "prefs-common-config": "CSS/JavaScript condiviso pe tutte e pelle:",
        "prefs-reset-intro": "L'è poscibile doeuviâ sta pagina pe rimpostâ e propie preferençe a quelle predefinie do scito.\nL'opiaçion a no poeu ese annullâ.",
        "prefs-emailconfirm-label": "Conferma de l'e-mail:",
        "youremail": "Indirìsso email:",
index a375bcc..368a641 100644 (file)
        "userjspreview": "'''به یاد داشته باشید که شما فقط دارید جاوااسکریپت کاربری‌تان را امتحان می‌کنید/پیش‌نمایش آن را می‌بینید.'''\n'''این جاوااسکریپت هنوز ذخیره نشده‌است!'''",
        "sitecsspreview": "'''به یاد داشته باشید که شما فقط دارید پیش‌نمایش این سی‌اس‌اس را می‌بینید.'''\n'''این سی‌اس‌اس هنوز ذخیره نشده‌است!'''",
        "sitejspreview": "'''فراموش مکنید که شما فقط دارید پیش‌نمایش سی‌اس‌اس کاربری‌تان را می‌بینید.'''\n'''این سی‌اس‌اس هنوز ذخیره نشده‌است!'''",
-       "userinvalidcssjstitle": "'''هشدار:''' پوسته‌ای به نام «$1» وجود ندارد.\nبه یاد داشته باشید که صفحه‌های شخصی ‎.css و ‎.js باید عنوانی با حروف کوچک داشته باشند؛ نمونه: {{ns:user}}:فو/vector.css در مقابل {{ns:user}}:فو/Vector.css.",
+       "userinvalidconfigtitle": "'''هشدار:''' پوسته‌ای به نام «$1» وجود ندارد.\nبه یاد داشته باشید که صفحه‌های شخصی ‎.css و ‎.js باید عنوانی با حروف کوچک داشته باشند؛ نمونه: {{ns:user}}:فو/vector.css در مقابل {{ns:user}}:فو/Vector.css.",
        "updated": "(تازة سازی بیة)",
        "note": "'''نکته:'''",
        "previewnote": "'''به یاد داشته باشید که این فقط پیش‌نمایش است.'''\nتغییرات شما هنوز ذخیره نشده‌است!",
        "prefs-files": "فایل",
        "prefs-custom-css": "سی‌اس‌اس شخصی",
        "prefs-custom-js": "جاوااسکریپت شخصی",
-       "prefs-common-css-js": "سی‌اس‌اس/جاوااسکریپت مشترک برای تمام پوسته‌ها:",
+       "prefs-common-config": "سی‌اس‌اس/جاوااسکریپت مشترک برای تمام پوسته‌ها:",
        "prefs-reset-intro": "شما می‌توانید از این صفحه برای بازگرداندن تنظیمات خود به پیش‌فرض تارنما استفاده کنید.\nاین کار بازگشت‌ناپذیر است.",
        "prefs-emailconfirm-label": "تأیید ایمیل:",
        "youremail": "ایمیل:",
index 6f924de..7762da1 100644 (file)
        "prefs-files": "Archivi",
        "prefs-custom-css": "CSS personalizàt",
        "prefs-custom-js": "JavaScript personalizat",
-       "prefs-common-css-js": "CSS/JavaScript cundivis per töte le skin:",
+       "prefs-common-config": "CSS/JavaScript cundivis per töte le skin:",
        "prefs-emailconfirm-label": "Cunferma de l'e-mail:",
        "youremail": "E-mail",
        "username": "{{GENDER:$1|Nom ütent}}:",
index d2e5474..c77099f 100644 (file)
        "userjspreview": "<strong>د ڤیر داشتوٙئیت کئ شوما فأقأط می توٙنیت جاڤا ئسکئریپت کاریاری توٙنە پیش سئیل بأکیت. ڤئ ئیسئنی ئمایە نأبییە!</strong>",
        "sitecsspreview": "<strong>د ڤیر داشتوٙئیت کئ شوما فأقأط می توٙنیت ئی سی ئس ئس کاریاری توٙنە پیش سئیل بأکیت. ڤئ ئیسئنی ئمایە نأبییە!</strong>",
        "sitejspreview": "<strong>د ڤیر داشتوٙئیت کئ شوما فأقأط می توٙنیت ئی جاڤا ئسکئریپت کاریاری توٙنە پیش سئیل بأکیت. ڤئ ئیسئنی ئمایە نأبییە!</strong>",
-       "userinvalidcssjstitle": "<strong>زئنار:</strong> پوٙسە \"$1\" نیئش.\nسی ئس ئس جاڤأنە و بألگە یا جاڤا ئسکئریپت سأربألگ ڤا حأرفیا کوچئک نە ڤئ کار گئرئتە، ھأمچئنی {{ns:کاریار}}:فو/ڤئکتور.سی ئس ئس چی د ری ڤئ ری {{ns:کاریار}}:فو/ڤئکتور. سی ئس ئسە.",
+       "userinvalidconfigtitle": "<strong>زئنار:</strong> پوٙسە \"$1\" نیئش.\nسی ئس ئس جاڤأنە و بألگە یا جاڤا ئسکئریپت سأربألگ ڤا حأرفیا کوچئک نە ڤئ کار گئرئتە، ھأمچئنی {{ns:کاریار}}:فو/ڤئکتور.سی ئس ئس چی د ری ڤئ ری {{ns:کاریار}}:فو/ڤئکتور. سی ئس ئسە.",
        "updated": "(ڤئ هئنگوم سازی بییە)",
        "note": "'''نیسأنئن:'''",
        "previewnote": "فأقأط ئی پیش سئیل د ڤیرتوٙ با.\nآلئشت کاریاتوٙ ھأنی ئمایە نأبینە",
        "prefs-files": "جانیایا",
        "prefs-custom-css": "سی اس اس جاافتائه",
        "prefs-custom-js": "جاوا نیسسه جاافتائه",
-       "prefs-common-css-js": " سی اس اس/جاوا اسکریپت بهر بیه سی همه پوسه یا:",
+       "prefs-common-config": " سی اس اس/جاوا اسکریپت بهر بیه سی همه پوسه یا:",
        "prefs-reset-intro": "شما می تونیت ای بلگه سی د نو زنه کردن ترجیحات خوت وه شکل تیارگه پیش فرض وه کار بوونیت.\nیه ورئشت پذیر نئ.",
        "prefs-emailconfirm-label": "پش راست کردن انجومانامه:",
        "youremail": "أنجومانامە:",
index 873c63c..fcff91e 100644 (file)
        "userjspreview": "'''Nepamirškite, kad jūs tik testuojat/peržiūrit savo naudotojo JavaScript, jis dar nebuvo išsaugotas!'''",
        "sitecsspreview": "'''Nepamirškite, kad jūs tik peržiūrit šio CSS .'''! N!''' Tai dar nebuvo išsaugotas!'''",
        "sitejspreview": "'''Nepamirškite, kad jūs tik peržiūrit šis JavaScript kodas .'''! N!''' Tai dar nebuvo išsaugotas!'''",
-       "userinvalidcssjstitle": "'''Dėmesio:''' Nėra jokios išvaizdos „$1“. Nepamirškite, kad savo .css ir .js puslapiai naudoja pavadinimą mažosiomis raidėmis, pvz., {{ns:user}}:Foo/vector.css, o ne {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Dėmesio:''' Nėra jokios išvaizdos „$1“. Nepamirškite, kad savo .css ir .js puslapiai naudoja pavadinimą mažosiomis raidėmis, pvz., {{ns:user}}:Foo/vector.css, o ne {{ns:user}}:Foo/Vector.css.",
        "updated": "(Atnaujinta)",
        "note": "'''Pastaba:'''",
        "previewnote": "<strong>Nepamirškite, kad tai tik peržiūra, pakeitimai dar nėra išsaugoti!</strong>",
        "prefs-files": "Failai",
        "prefs-custom-css": "Asmeninis CSS",
        "prefs-custom-js": "Asmeninis JavaSript",
-       "prefs-common-css-js": "Bendras CSS/JS visoms išvaizdoms:",
+       "prefs-common-config": "Bendras CSS/JS visoms išvaizdoms:",
        "prefs-reset-intro": "Jūs galite pasinaudoti šiuo puslapiu, kad grąžintumėte savo nustatymus į svetainės numatytuosius.\nTai nebeatšaukiama.",
        "prefs-emailconfirm-label": "El. pašto patvirtinimas:",
        "youremail": "El. paštas:",
        "protect-othertime": "Kitas laikas:",
        "protect-othertime-op": "kitas laikas",
        "protect-existing-expiry": "Esamas galiojimo laikas: $3, $2",
-       "protect-existing-expiry-infinity": "Esamas galiojimo laikas: begalinis",
+       "protect-existing-expiry-infinity": "Esamas galiojimo laikas: neribotas",
        "protect-otherreason": "Kita/papildoma priežastis:",
        "protect-otherreason-op": "Kita priežastis",
        "protect-dropdown": "*Įprastos užrakinimo priežastys\n** Intensyvus vandalizmas\n** Nuolatinis nepageidautinų nuorodų dėliojimas\n** Beprasmis redagavimo karas\n** Didelės svarbos puslapis\n** Pakartotinis ištrinto puslapio atkūrinėjimas",
index c68c667..8cfa6cf 100644 (file)
        "prefs-files": "Taksa",
        "prefs-custom-css": "CSS hman",
        "prefs-custom-js": "JavaScript hman",
-       "prefs-common-css-js": "CSS inţawm/vun zawng zawng tana JavaScript",
+       "prefs-common-config": "CSS inţawm/vun zawng zawng tana JavaScript",
        "prefs-reset-intro": "He phêk hi ränghmuna i duhthlansa tihdanglam nan i hmang thei.\nA sûtlêt theih loh.",
        "prefs-emailconfirm-label": "E-chenhmun tihchianna:",
        "youremail": "E-chenhmun:",
index 96c7c65..f56d350 100644 (file)
        "history": "hronoloģija",
        "history_short": "Vēsture",
        "history_small": "vēsture",
-       "updatedmarker": "atjaunināti kopš pēdējā apmeklējuma",
+       "updatedmarker": "atjaunināts kopš mana pēdējā apmeklējuma",
        "printableversion": "Drukājama versija",
        "permalink": "Pastāvīgā saite",
        "print": "Drukāt",
        "prefs-files": "Faili",
        "prefs-custom-css": "Personīgais CSS",
        "prefs-custom-js": "Personīgais JS",
-       "prefs-common-css-js": "Koplietojams CSS/JavaScript visās apdarēs:",
+       "prefs-common-config": "Koplietojams CSS/JavaScript visās apdarēs:",
        "prefs-emailconfirm-label": "E-pasta statuss:",
        "youremail": "Tava e-pasta adrese:",
        "username": "{{GENDER:$1|Lietotājvārds}}:",
index e121124..389dedd 100644 (file)
        "userjspreview": "'''預覽簿JavaScript。'''\n'''尚未儲焉!'''",
        "sitecsspreview": "'''預覽此CSS。'''\n'''尚未儲焉!'''",
        "sitejspreview": "'''預覽此JavaScript。'''\n'''尚未儲焉!'''",
-       "userinvalidcssjstitle": "'''警:'''\"$1\"無此面版。自製者,全名務小寫,如{{ns:user}}:Foo/vector.css 而非{{ns:user}}:Foo/Vector.css",
+       "userinvalidconfigtitle": "'''警:'''\"$1\"無此面版。自製者,全名務小寫,如{{ns:user}}:Foo/vector.css 而非{{ns:user}}:Foo/Vector.css",
        "updated": "(新)",
        "note": "'''註'''",
        "previewnote": "'''此乃預覽,尚未儲焉。'''",
        "prefs-files": "檔",
        "prefs-custom-css": "定之CSS",
        "prefs-custom-js": "定之JavaScript",
-       "prefs-common-css-js": "共CSS/JavaScript於面版:",
+       "prefs-common-config": "共CSS/JavaScript於面版:",
        "prefs-reset-intro": "爾用頁重設至預之設。無修之也。",
        "prefs-emailconfirm-label": "確郵:",
        "youremail": "郵:",
index f76bfc0..8cc626b 100644 (file)
        "userjspreview": "''' मोन राखू जे अहाँ मात्र अपन प्रयोक्ता  जावास्क्रिप्टक पूर्वदृश्य देख रहल छी।'''\n''' ई अखन धरि संरक्षित नै भऽ सकल!'''",
        "sitecsspreview": "''' मोन राखू जे अहाँ मात्र ऐ  सी.एस.एस. क पूर्वदृश्य देख रहल छी।'''\n''' ई अखन धरि संरक्षित नै भऽ सकल!'''",
        "sitejspreview": "''' मोन राखू जे अहाँ मात्र ऐ  जावास्क्रिप्टक पूर्वदृश्य देख रहल छी।'''\n''' ई अखन धरि संरक्षित नै भऽ सकल!'''",
-       "userinvalidcssjstitle": "'''चेतौनी:''' ऐ मे कोनो आवरण \"$1\" नै अछि।\nबनाएल .css आ .js पन्ना लघ्वक्षरक शीर्षकक प्रयोग करैत अछि, जेना {{ns:user}}:Foo/vector.css एकर विरुद्ध {{ns:user}}:Foo/Vector.css ।",
+       "userinvalidconfigtitle": "'''चेतौनी:''' ऐ मे कोनो आवरण \"$1\" नै अछि।\nबनाएल .css आ .js पन्ना लघ्वक्षरक शीर्षकक प्रयोग करैत अछि, जेना {{ns:user}}:Foo/vector.css एकर विरुद्ध {{ns:user}}:Foo/Vector.css ।",
        "updated": "(अद्यतन  कएल)",
        "note": "<strong>टिप्पणी:</strong>",
        "previewnote": "<strong>मोन राखू ई मात्र पूर्वावलोकन छी।</strong>\nअहाँक परिवर्तन अखन धरि सङ्ग्रह नै कएल गेल अछि!",
        "prefs-files": "सञ्चिकासभ",
        "prefs-custom-css": "खास सियसयस",
        "prefs-custom-js": "खास जावास्क्रिप्ट",
-       "prefs-common-css-js": "सभ रूप लेल साझी सी.एस.एस./ जावास्क्रिप्ट:",
+       "prefs-common-config": "सभ रूप लेल साझी सी.एस.एस./ जावास्क्रिप्ट:",
        "prefs-reset-intro": "अहाँ ऐ पन्नाक प्रयोग अपन विकल्पकेँ पूर्वनिविष्ट रूपेँ जाल पुनर्निधारित करबा लेल कऽ सकै छी।\nई बदलल नै जा सकैए।",
        "prefs-emailconfirm-label": "ई-पत्र पुष्टि:",
        "youremail": "ई-पत्र:",
index 72dcde6..7a3e818 100644 (file)
        "userjspreview": "'''Eling ya Rika kuwe mung lagi nampilna pratayang sekang JavaScript-e Rika.'''\n'''Pratayang kiye anu durung disimpen!'''",
        "sitecsspreview": "'''Eling ya Rika kuwe mung lagi nampilna pratayang sekang CSS kiye.'''\n'''Pratayang kiye anu durung disimpen!'''",
        "sitejspreview": "'''Eling ya Rika kuwe mung lagi nampilna pratayang sekang kode JavaScript kiye.'''\n'''Pratayang kiye anu durung disimpen!'''",
-       "userinvalidcssjstitle": "'''Pènget:''' Kulit \"$1\" ora ditemokna. Eling ya nek kaca .css lan .js kuwe nggunakna aksara cilik, conto {{ns:user}}:Foo/vector.css lan dudu {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Pènget:''' Kulit \"$1\" ora ditemokna. Eling ya nek kaca .css lan .js kuwe nggunakna aksara cilik, conto {{ns:user}}:Foo/vector.css lan dudu {{ns:user}}:Foo/Vector.css.",
        "updated": "(Dianyari)",
        "note": "'''Cathetan:'''",
        "previewnote": "'''Eling ya kiye tembe pratayang thok.'''\nOwahane Rika durung disimpen!",
        "prefs-files": "Berkas",
        "prefs-custom-css": "CSS pribadi",
        "prefs-custom-js": "JavaScript pribadi",
-       "prefs-common-css-js": "Mbagekna CSS/JavaScript nggo kabeh kulit:",
+       "prefs-common-config": "Mbagekna CSS/JavaScript nggo kabeh kulit:",
        "prefs-reset-intro": "Rika teyeng nggunakna kaca kiye nggo mbalekna preferensi-ne Rika balik maring setelan baku situs.\nPambalikan kiye ora teyeng dibatalna.",
        "prefs-emailconfirm-label": "Konfirmasi imel:",
        "youremail": "Imel:",
index 33f6892..0195f74 100644 (file)
        "userjsyoucanpreview": "'''Мялень максома:''' Ванфтомада инголе нолдак тевс 'Васень няфтема' пунять тонь од CSS эли JS файлть варжаманкса.",
        "usercsspreview": "Киртть мяльсот тя аньцек тонь CSS файлцень васень няфтемац. Сон нинге изь ванфтов!'''",
        "userjspreview": "'''Киртть мяльсот тя аньцек тонь JavaScript файлть варжамась/васень няфтемась, сон нинге изь ванфтов!'''",
-       "userinvalidcssjstitle": "'''Инголе мярьгома:''' Аш тема файл \"$1\" мазопнеманкса. Киртть мяльсот .css эди .js лопас путови аньцек ёмла тяшкса коняксне, кепотьксонди {{ns:user}}:Foo/лем.css афи {{ns:user}}:Foo/Лем.css.",
+       "userinvalidconfigtitle": "'''Инголе мярьгома:''' Аш тема файл \"$1\" мазопнеманкса. Киртть мяльсот .css эди .js лопас путови аньцек ёмла тяшкса коняксне, кепотьксонди {{ns:user}}:Foo/лем.css афи {{ns:user}}:Foo/Лем.css.",
        "updated": "(Одонзаф)",
        "note": "'''Шарфтк мяльце:'''",
        "previewnote": "'''Киртть мяльсот, тя аньцек васень няфтемась.''' Тонь полафтоматне нинге исть ванфтов!",
index c775763..f626f0c 100644 (file)
        "userjspreview": "\n'''Tadidio fa manandrana/mijery tsipalotra ny fivoakan'ny JavaScript-nao fotsiny ihany ianao fa tsy mbola voatahiry akory izy io!'''",
        "sitecsspreview": "'''Fantaro fa manao topi-maso an'ity CSS ity fotsiny ianao'''.\n'''Mbola tsy voatahiry ilay izy !'''",
        "sitejspreview": "'''Tadidio fa manao topi-maso renifango JavaScript ianao.'''\n'''Mbola tsy notahirizina izy io !'''",
-       "userinvalidcssjstitle": "'''Tandremo''' : Tsy misy fampiankanjoana « $1 » izany.\nTadidio fa mampiasa soramadinika ny lohatenin'ny pejinao manan-tovana *.css sy *.js, ohatra {{ns:user}}:Foo/vector.css fa tsy {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Tandremo''' : Tsy misy fampiankanjoana « $1 » izany.\nTadidio fa mampiasa soramadinika ny lohatenin'ny pejinao manan-tovana *.css sy *.js, ohatra {{ns:user}}:Foo/vector.css fa tsy {{ns:user}}:Foo/Vector.css.",
        "updated": "(Nohavaozina)",
        "note": "'''Fanamarihana:'''",
        "previewnote": "'''Fantaro fa topi-maso fotsiny ity.'''\nMbola tsy voatahiry ny fanovanao !",
        "prefs-files": "Rakitra",
        "prefs-custom-css": "CSS manokana",
        "prefs-custom-js": "Javascript manokana",
-       "prefs-common-css-js": "JavaScript ary CSS zaraina ho an'ny fiankanjoana rehetra:",
+       "prefs-common-config": "JavaScript ary CSS zaraina ho an'ny fiankanjoana rehetra:",
        "prefs-reset-intro": "Azonao ampiasaina ity pejy ity mba hamerina ny safidinao amin'izay safidy tsipalotr'ilay sehatra. Tsy azo averina io.",
        "prefs-emailconfirm-label": "Famarinana ny imailaka :",
        "youremail": "Imailaka:",
index d34ce7e..d525160 100644 (file)
        "userjspreview": "'''Ingeklah bahawa nan Sanak liek hanyolah pratayang JavaScript Sanak, dan bahawa pratayang tasabuik alun disimpan!'''",
        "sitecsspreview": "'''Ingeklah bahawa Sanak hanyo manampilan pratayang dari CSS iko.'''\n'''Parubahan alun disimpan!'''",
        "sitejspreview": "<strong>Ingek! Sanak hanyo manampilan pratonton kode JavaScript ko. Parubahan alun basimpan!</strong>",
-       "userinvalidcssjstitle": "'''Paringatan:''' Kulik \"$1\" indak ditamuan. Harap diingek bahawa laman .css dan .js manggunokan huruf kecil, contoh {{ns:user}}:Foo/vector.css dan bukannyo {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Paringatan:''' Kulik \"$1\" indak ditamuan. Harap diingek bahawa laman .css dan .js manggunokan huruf kecil, contoh {{ns:user}}:Foo/vector.css dan bukannyo {{ns:user}}:Foo/Vector.css.",
        "updated": "(Dipabaharui)",
        "note": "'''Catatan:'''",
        "previewnote": "'''Ingek iko hanyo pratonton'''\nParubahan Sanak alun disimpan!",
        "prefs-files": "Berkas",
        "prefs-custom-css": "CSS paribadi",
        "prefs-custom-js": "JS paribadi",
-       "prefs-common-css-js": "CSS/JS untuak kasado kulik:",
+       "prefs-common-config": "CSS/JS untuak kasado kulik:",
        "prefs-reset-intro": "Angku dapek manggunokan laman ko untuak mangambalikan pangaturan ka setelan baku situs ko.\nPangambalian pangaturan indak dapek dibatalan.",
        "prefs-emailconfirm-label": "Surel konfirmasi:",
        "youremail": "Surel:",
index 88b8dbd..a1f615a 100644 (file)
@@ -21,7 +21,8 @@
                        "Matma Rex",
                        "Kaldari",
                        "Xð",
-                       "Srdjan m"
+                       "Srdjan m",
+                       "Acamicamacaraca"
                ]
        },
        "tog-underline": "Потцртување на врски:",
        "userjspreview": "'''Запомнете дека ова е само преглед на вашиот JavaScript код, страницата сè уште не е зачувана!'''",
        "sitecsspreview": "'''Запомнете дека ова е само преглед на овој CSS-код.'''\n'''Сè уште не е зачуван!'''",
        "sitejspreview": "'''Запомнете дека ова е само преглед на овој JavaScript-код.'''\n'''Сè уште не е зачуван!'''",
-       "userinvalidcssjstitle": "'''Предупредување:''' Нема руво „$1“.\nЗапомнете дека сопствените .css и .js страници имаат имиња со мали букви, пр. {{ns:user}}:Некој/vector.css наместо {{ns:user}}:Некој/Vector.css.",
+       "userinvalidconfigtitle": "'''Предупредување:''' Нема руво „$1“.\nЗапомнете дека сопствените .css и .js страници имаат имиња со мали букви, пр. {{ns:user}}:Некој/vector.css наместо {{ns:user}}:Некој/Vector.css.",
        "updated": "(Подновено)",
        "note": "'''Напомена:'''",
        "previewnote": "'''Имајте предвид дека ова е само преглед.'''\nПромените сè уште не се зачувани!",
        "prefs-files": "Податотеки",
        "prefs-custom-css": "Посебно CSS",
        "prefs-custom-js": "Посебно JS",
-       "prefs-common-css-js": "Заеднички CSS/JS за сите изгледи:",
+       "prefs-common-config": "Заеднички CSS/JS за сите изгледи:",
        "prefs-reset-intro": "Може да ја користите оваа страница за враќање на вашите нагодувања на основно-зададените нагодувања на викито. Ова дејство е неповратно.",
        "prefs-emailconfirm-label": "Потврда на е-пошта:",
        "youremail": "Е-пошта:",
index 7f068cd..1706d9d 100644 (file)
        "userjspreview": "'''താങ്കൾ താങ്കളുടെ സ്വന്തം ജാവസ്ക്രിപ്റ്റ് പ്രിവ്യൂ ചെയ്യുക മാത്രമേ ചെയ്യുന്നുള്ളൂ എന്ന കാര്യം ഓർമ്മിക്കുക. ഇതു സേവ് ചെയ്തിട്ടില്ല!'''",
        "sitecsspreview": "'''താങ്കൾ ഈ സി.എസ്.എസ്.ന്റെ പ്രിവ്യൂ കാണുക മാത്രമേ ചെയ്യുന്നുള്ളൂ എന്ന കാര്യം ഓർമ്മിക്കുക.'''\n'''ഇതു സേവ് ചെയ്തിട്ടില്ല!'''",
        "sitejspreview": "'''താങ്കൾ ഈ ജാവസ്ക്രിപ്റ്റ് കോഡിന്റെ പ്രിവ്യൂ കാണുക മാത്രമേ ചെയ്യുന്നുള്ളൂ എന്ന കാര്യം ഓർമ്മിക്കുക.'''\n'''ഇതു സേവ് ചെയ്തിട്ടില്ല!'''",
-       "userinvalidcssjstitle": "'''മുന്നറിപ്പ്:''' \"$1\" എന്ന പേരിൽ ഒരു ദൃശ്യരൂപം ഇല്ല. '''.css''' ഉം '''.js''' ഉം താളുകൾ ഇംഗ്ലീഷ് ചെറിയക്ഷര തലക്കെട്ട് ആണ്‌ ഉപയോഗിക്കുന്നതെന്നു ദയവായി ഓർക്കുക. ഉദാ: {{ns:user}}:Foo/Vector.css എന്നതിനു പകരം {{ns:user}}:Foo/vector.css എന്നാണു ഉപയോഗിക്കേണ്ടത്.",
+       "userinvalidconfigtitle": "'''മുന്നറിപ്പ്:''' \"$1\" എന്ന പേരിൽ ഒരു ദൃശ്യരൂപം ഇല്ല. '''.css''' ഉം '''.js''' ഉം താളുകൾ ഇംഗ്ലീഷ് ചെറിയക്ഷര തലക്കെട്ട് ആണ്‌ ഉപയോഗിക്കുന്നതെന്നു ദയവായി ഓർക്കുക. ഉദാ: {{ns:user}}:Foo/Vector.css എന്നതിനു പകരം {{ns:user}}:Foo/vector.css എന്നാണു ഉപയോഗിക്കേണ്ടത്.",
        "updated": "(പുതുക്കിയിരിക്കുന്നു)",
        "note": "'''പ്രത്യേക ശ്രദ്ധയ്ക്ക്:'''",
        "previewnote": "'''ഇതൊരു പ്രിവ്യൂ മാത്രമാണെന്ന് ഓർക്കുക.'''\nതാങ്കൾ വരുത്തിയ മാറ്റങ്ങൾ ഇതുവരെ സേവ് ചെയ്തിട്ടില്ല!",
        "prefs-files": "പ്രമാണങ്ങൾ",
        "prefs-custom-css": "സ്വന്തം സി.എസ്.എസ്.",
        "prefs-custom-js": "സ്വന്തം ജെ.എസ്.",
-       "prefs-common-css-js": "എല്ലാ ദൃശ്യരൂപങ്ങൾക്കുമായി പങ്ക് വെയ്ക്കപ്പെട്ട സി.എസ്.എസ്./ജെ.എസ്.:",
+       "prefs-common-config": "എല്ലാ ദൃശ്യരൂപങ്ങൾക്കുമായി പങ്ക് വെയ്ക്കപ്പെട്ട സി.എസ്.എസ്./ജെ.എസ്.:",
        "prefs-reset-intro": "സൈറ്റിൽ സ്വതേയുണ്ടാവേണ്ട ക്രമീകരണങ്ങൾ പുനഃക്രമീകരിക്കാൻ താങ്കൾക്ക് ഈ താൾ ഉപയോഗിക്കാവുന്നതാണ്.\nഇത് തിരിച്ചു ചെയ്യാൻ സാദ്ധ്യമല്ല.",
        "prefs-emailconfirm-label": "ഇമെയിൽ സ്ഥിരീകരണം:",
        "youremail": "ഇമെയിൽ:",
index 78ffe43..25f3d25 100644 (file)
        "userjspreview": "'''Та өөрийн хэрэглэгчийн ЖаваСкриптийг зөвхөн урьдчилан харж байгаа бөгөөд энэ нь хараахан хадгалагдаагүй байгаа гэдгийг анхаарна уу!'''",
        "sitecsspreview": "'''Танд энэ CSS зөвхөн урьдчилан харагдаж гэдгээ санаарай.'''\n'''Хараахан хадгалагдаагүй байгаа гэдгийг анхаарна уу!'''",
        "sitejspreview": "'''Энэ JavaScript код танд л харагдах хэлбэрт байна'''\n'''Хараахан хадгалагдаагүй байгааг анзаарна уу!'''",
-       "userinvalidcssjstitle": "'''Анхаар:''' \"$1\" гэсэн арьс байхгүй байна.\nӨөрсдийн .css болон .js хуудсуудыг нэрлэхэд жижиг үсэг хэрэглэдэг болохыг сануулж байна. Жишээ нь: {{ns:user}}:Foo/vector.css гэж л хэрэглэх бөгөөд {{ns:user}}:Foo/Vector.css гэхгүй.",
+       "userinvalidconfigtitle": "'''Анхаар:''' \"$1\" гэсэн арьс байхгүй байна.\nӨөрсдийн .css болон .js хуудсуудыг нэрлэхэд жижиг үсэг хэрэглэдэг болохыг сануулж байна. Жишээ нь: {{ns:user}}:Foo/vector.css гэж л хэрэглэх бөгөөд {{ns:user}}:Foo/Vector.css гэхгүй.",
        "updated": "(Шинэчлэгдсэн)",
        "note": "'''Анхааруулга:'''",
        "previewnote": "'''Энэ бол зөвхөн урьдчилж харсан байдал.'''\nТаны хийсэн өөрчлөлтүүдийг одоохондоо хадгалаагүй байгаа!",
        "prefs-files": "Файлууд",
        "prefs-custom-css": "Өөрийн сонгосон CSS",
        "prefs-custom-js": "Өөрийн сонгосон JS",
-       "prefs-common-css-js": "Бүх скинд ашиглагдах CSS/ЖаваСкрипт:",
+       "prefs-common-config": "Бүх скинд ашиглагдах CSS/ЖаваСкрипт:",
        "prefs-reset-intro": "Та энэ хуудсыг ашиглан өөрийн тохиргоог сайтын анхны тохиргооо руу шилжүүлэх боломжтой.\nЭнэ үйлдлийг буцаах боломжгүй.",
        "prefs-emailconfirm-label": "Мэйлийн баталгаажуулалт:",
        "youremail": "Мэйл хаяг:",
index bd57dcc..a816be1 100644 (file)
        "tagline": "{{SITENAME}} कडून",
        "help": "साहाय्य",
        "search": "शोधा",
-       "search-ignored-headings": " #<!-- leave this line exactly as it is --> <pre>\n# Headings that will be ignored by search.\n# Changes to this take effect as soon as the page with the heading is indexed.\n# You can force page reindexing by doing a null edit.\n# The syntax is as follows:\n#   * Everything from a \"#\" character to the end of the line is a comment.\n#   * Every non-blank line is the exact title to ignore, case and everything.\nReferences\nExternal links\nSee also\n #</pre> <!-- leave this line exactly as it is -->",
+       "search-ignored-headings": " #<!-- हे असेच सोडा --> <pre>\n# शोधामधून ही शिर्षके वगळली जातील.\n# शिर्षकांमधील बदल अनुक्रमाणिकेत आल्याबरोबरच पुढील बदल आपसूकच होतील.\n# तुम्ही पानांमध्ये कसलाही बदल न करता पुन्हा साठवून ठेवल्यास पान आपसूकच अनुक्रमाणिकेत पुन्हा जोडले जाईल.\n# त्याचा सिन्टेक्स पुढीलप्रमाणे आहे:\n#   * \"#\" इथपासून ओळीच्या शेवटापर्यंतचे सर्वकाही मत म्हणून नोंदवलेले आहे.\n#   * प्रत्येक रिकामी नसलेली ओळ ही दुर्लक्षित करण्यासाठीचे शिर्षक आहे, पाने आणि सर्वकाही.\nसंदर्भयादी\nबाह्यदुवे\nहे ही पहा\n #</pre> <!-- हे असेच सोडा -->",
        "searchbutton": "शोधा",
        "go": "चला",
        "searcharticle": "जा",
        "accmailtext": "[[User talk:$1|$1]] यांसाठी अनियतक्रमाने निर्मित केलेला परवलीचा शब्द $2 यांना पाठवण्यात आला आहे.\n\nया नवीन खात्यासाठीचा परवलीचा शब्द,सनोंद-प्रवेश घेतल्यावर [[Special:ChangePassword|परवलीचा शब्द बदला]] येथे बदलता येईल.",
        "newarticle": "(नवीन लेख)",
        "newarticletext": "आपण सध्या अस्तित्त्वात नसलेल्या पानाच्या दुव्याचा मागोवा घेत आला आहात.\nहे पान नव्याने तयार करण्यासाठी खालील पेटीत टंकन करणे सुरु करा(अधिक माहितीसाठी [$1 साहाय्य पान] बघा).\n\nजर आपण येथे चुकून आला असाल तर ब्राउझरच्या  <strong>परत</strong>(बॅक) कळीवर टिचकी द्या.",
-       "anontalkpagetext": "---- ''हे चर्चापान अशा अज्ञात सदस्यासाठी आहे, ज्यांनी खाते तयार केलेले नाही किंवा त्याचा वापर करत नाहीत. त्यांच्या ओळखीसाठी आम्ही आंतरजाल अंकपत्ता वापरतो आहोत. असा अंकपत्ता बऱ्याच लोकांचा एकच असू शकतो. जर आपण अज्ञात सदस्य असाल आणि आपल्याला काही अप्रासंगिक संदेश मिळाला असेल तर कृपया [[Special:UserLogin| खाते तयार करा]] किंवा [[Special:CreateAccount|सनोंद-प्रवेश करा]] ज्यामुळे, पुढे असे गैरसमज होणार नाहीत.''",
+       "anontalkpagetext": "<em>हे चर्चापान अशा अज्ञात सदस्यासाठी आहे, ज्यांनी खाते तयार केलेले नाही किंवा त्याचा वापर करत नाहीत.</em> \nत्यांच्या ओळखीसाठी आम्ही आंतरजाल अंकपत्ता वापरतो आहोत. असा अंकपत्ता बऱ्याच लोकांचा एकच असू शकतो. \nजर आपण अज्ञात सदस्य असाल आणि आपल्याला काही अप्रासंगिक संदेश मिळाला असेल तर कृपया [[Special:CreateAccount| खाते तयार करा]] किंवा [[Special:CreateAccount|सनोंद-प्रवेश करा]] ज्यामुळे, पुढे असे गैरसमज होणार नाहीत.",
        "noarticletext": "या लेखात सध्या काहीही मजकूर नाही.\nतुम्ही विकिपीडियावरील इतर लेखांमध्ये या [[Special:Search/{{PAGENAME}}| मथळ्याचा शोध घेऊ शकता]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} इतर नोंदी शोधा],\nकिंवा हा लेख [{{fullurl:{{FULLPAGENAME}}|action=edit}}तयार करू शकता]</span>.",
        "noarticletext-nopermission": "सध्या या लेखात  काहीही मजकूर नाही.\nतुम्ही विकिपीडियावरील इतर लेखांमध्ये [[Special:Search/{{PAGENAME}}| या मथळ्याचा शोध घेऊ शकता]], <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAME}}}}आपण या लेखाच्या इतर नोंदी शोधा]</span>,परंतु, आपणास हा लेख लिहीण्याची परवानगी देण्यात येउ शकत नाही.",
        "missing-revision": "\"{{FULLPAGENAME}}\" या लेखाचे #$1 हे संस्करण अस्तित्वात नाही.वगळल्या गेलेल्या लेखपानाच्या जुन्या इतिहास-दुव्याचे अनुसरण केल्यामुळे असे होते.याबाबत विस्तृत माहिती  [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} वगळलेल्या नोंदी]येथे बघता येईल.",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" सदस्य खात्याची नोंद नाही. कृपया हे पान तुम्ही संपादित किंवा नव्याने तयार करू इच्छिता काय याबद्दल विचार करा.",
        "userpage-userdoesnotexist-view": "सदस्यखाते \"$1\"  हे नोंदलेले नाही.",
        "blocked-notice-logextract": "हा सदस्य सध्या प्रतिबंधित आहे.\nखाली, सर्वांत नवीनतम प्रतिबंधन नोंदप्रविष्टी संदर्भासाठी दिली आहे:",
-       "clearyourcache": "'''सूचना:''' जतन केल्यावर बदल दिसण्यासाठी तुम्हाला कदाचित न्याहाळकाची सय टाळायला लागेल. असे करण्यासाठी - \n\n*'''फायरफॉक्स / सफारी:''' साठी ''Reload'' हे टिचकतांना ''Shift'' ही कळ दाबून ठेवा, किंवा ''Ctrl-F5'' अथवा ''Ctrl-R'' कळा एकत्रितपणे दाबा (मॅकसाठी ''⌘-R'').\n\n*'''गूगल क्रोम:''' साठी ''Ctrl-Shift-R'' कळा एकत्रितपणे दाबा (मॅकसाठी ''⌘-Shift-R'')\n\n*'''इंटरनेट एक्सप्लोअरर:''' ''Refresh'' करतांना ''Ctrl'' कळ दाबून ठेवा, किंवा त्याऐवजी ''Ctrl-F5'' दाबा.\n\n*'''कॉन्क्वरर:''' '''Reload''' दाबा किंवा ''F5'' दाबा\n\n*'''ऑपेरा:''' ''Tools → Preferences'' मधून सय रिकामी करा",
+       "clearyourcache": "<strong>नोंद:</strong> साठवून ठेवल्यानंतर बदल पहाण्यासाठी कदाचित तुमच्या ब्राऊजरच्या कॅचेला बायपास करावे लागेल.\n* <strong>फ़ायरफ़ॉक्स / सफ़ारी:</strong> धरुन ठेवा <em>Shift</em> टिचकी मारताना <em>Reload</em>, किंवा हे दाबताना <em>Ctrl-F5</em> किंवा <em>Ctrl-R</em> (<em>⌘-R</em> मॅकवर)\n* <strong>गुगल क्रोम:</strong> दाबा <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> मॅकसाठी)\n* <strong>ओपेरा:</strong> कडे जा <em>Menu → Settings</em> (<em>ओपेरा → पसंतीक्रम </em> on a Mac) आणि मग <em>गोपनियता आणि सुरक्षा → ब्राउजिंग डाटा काढून टाका → कॅचे छायाचित्रे आणि धारिणी</em>.",
        "usercssyoucanpreview": "'''टीप:'''तुमचे नवे सीएसएस जतन करण्यापूर्वी 'झलक पहा' कळ वापरा.",
        "userjsyoucanpreview": "'''टीप:''' तुमचा नवा जावास्क्रिप्ट जतन करण्यापूर्वी 'झलक पहा' कळ वापरा.",
        "usercsspreview": "'''तुम्ही तुमच्या सी.एस.एस.ची केवळ झलक पहात आहात, ती अजून जतन केलेली नाही हे लक्षात घ्या.'''",
        "userjspreview": "'''तुम्ही तुमची सदस्य जावास्क्रिप्ट तपासत आहात किंवा झलक पहात आहात ,ती अजून जतन केलेली नाही हे लक्षात घ्या!'''",
        "sitecsspreview": "'''तुम्ही तुमच्या सी.एस.एस.ची केवळ झलक पहात आहात, ती अजून जतन केलेली नाही हे लक्षात घ्या.'''",
        "sitejspreview": "'''तुम्ही तुमच्या जावास्क्रिप्टची केवळ झलक पहात आहात, ती अजून जतन केलेली नाही हे लक्षात घ्या.'''",
-       "userinvalidcssjstitle": "'''सावधान:''' \"$1\" अशी त्वचा नाही.custom .css आणि .js पाने lowercase title वापरतात हे लक्षात घ्या, उदा. {{ns:user}}:Foo/vector.css या विरुद्ध {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''सावधान:''' \"$1\" अशी त्वचा नाही.custom .css आणि .js पाने lowercase title वापरतात हे लक्षात घ्या, उदा. {{ns:user}}:Foo/vector.css या विरुद्ध {{ns:user}}:Foo/Vector.css.",
        "updated": "(अद्यतन केले)",
        "note": "'''सूचना:'''",
        "previewnote": "'''लक्षात ठेवा की ही फक्त झलक आहे''', बदल अजून जतन करण्यात आलेले नाहीत.",
        "prefs-files": "संचिका",
        "prefs-custom-css": "सीएसएस पद्धत बदला",
        "prefs-custom-js": "जावास्क्रिप्ट पद्धत बदला",
-       "prefs-common-css-js": "मिळून वापरलेले सर्व त्वचांसाठींचे सीएसएस / जावास्क्रिप्ट:",
+       "prefs-common-config": "मिळून वापरलेले सर्व त्वचांसाठींचे सीएसएस / जावास्क्रिप्ट:",
        "prefs-reset-intro": "आपण या पानाचा वापर, या संकेतस्थळच्या अविचलनुसार, आपला पसंतीक्रम पुनर्स्थापनेसाठी करू शकता.",
        "prefs-emailconfirm-label": "विपत्र निश्चितीकरण:",
        "youremail": "विपत्र:",
        "htmlform-user-not-valid": "<strong>$1</strong> हे वैध सदस्यनाम नाही.",
        "logentry-delete-delete": "$1 {{GENDER:$2|वगळलेले पान}} $3",
        "logentry-delete-delete_redir": "$1 ने $3 हे पुनर्निर्देशन उपरीलेखन (ओव्हररायटिंग) करून {{GENDER:$2|वगळले}}",
-       "logentry-delete-restore": "$1 {{GENDER:$2|पुनर्स्थापित पृष्ठ}} $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|पुनर्स्थापित पृष्ठ}} $3 ($4)",
        "logentry-delete-event": "$1 ने $3 वर{{PLURAL:$5|नोंद-प्रसंग|$5 नोंद प्रसंगांची}} दृष्यता{{GENDER:$2|बदलली}}:$4",
        "logentry-delete-revision": "$1 ने $3 पानावर{{PLURAL:$5|आवृत्ती|$5 आवृत्यांची}} दृष्यता{{GENDER:$2|बदलली}}:$4",
        "logentry-delete-event-legacy": "$1 ने $3 वर नोंद प्रसंगांची {{GENDER:$2|बदलली}}",
index 74271eb..334e350 100644 (file)
        "userjspreview": "'''Ingat bahawa anda hanya menguji/melihat pralihat JavaScript anda, ia belum lagi disimpan!'''",
        "sitecsspreview": "'''Ingat bahawa anda cuma melihat pralihat CSS ini.'''\n'''Ia belum lagi disimpan!'''",
        "sitejspreview": "'''Ingat bahawa anda cuma mempralihat kod JavaScript ini.'''\n'''Ia belum lagi disimpan!'''",
-       "userinvalidcssjstitle": "'''Amaran:''' Rupa \"$1\" tidak wujud. Ingat bahawa laman tempahan .css dan .js menggunakan tajuk berhuruf kecil, contohnya {{ns:user}}:Anu/vector.css tidak sama dengan {{ns:user}}:Anu/Vector.css.",
+       "userinvalidconfigtitle": "'''Amaran:''' Rupa \"$1\" tidak wujud. Ingat bahawa laman tempahan .css dan .js menggunakan tajuk berhuruf kecil, contohnya {{ns:user}}:Anu/vector.css tidak sama dengan {{ns:user}}:Anu/Vector.css.",
        "updated": "(Dikemas kini)",
        "note": "'''Catatan:'''",
        "previewnote": "'''Ingatlah bahawa ini hanya pralihat.'''\nPerubahan anda belum disimpan!",
        "prefs-files": "Fail",
        "prefs-custom-css": "CSS tempahan",
        "prefs-custom-js": "JS tempahan",
-       "prefs-common-css-js": "CSS/JavaScript kongsi untuk semua rupa:",
+       "prefs-common-config": "CSS/JavaScript kongsi untuk semua rupa:",
        "prefs-reset-intro": "Anda boleh menggunakan laman ini untuk menetapkan semula keutamaan anda kepada tetapan asali.\nTindakan ini tidak boleh dibatalkan.",
        "prefs-emailconfirm-label": "Pengesahan e-mel:",
        "youremail": "E-mel:",
index 55f3314..6f88ae3 100644 (file)
        "userjspreview": "'''Ftakar li inti qiegħed biss tipprova/tara dehra proviżorja tal-JavaScript personali; il-modifiki li għamilt għad iridu jiġu salvati!'''",
        "sitecsspreview": "'''Ftakar li din hija biss dehra proviżorja tas-CSS. Il-modifiki għadhom ma ġewx salvati!'''",
        "sitejspreview": "'''Ftakar li din hija biss dehra proviżorja tal-JavaScript. Il-modifiki għadhom ma ġewx salvati!'''",
-       "userinvalidcssjstitle": "'''Twissija:''' M'hemm l-ebda aspett bl-isem \"$1\".\nFtakar li l-paġni .css u .js personalizzati għandhom l-ewwel ittra tat-titlu żgħira, eż. {{ns:user}}:Foo/vector.css u mhux {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Twissija:''' M'hemm l-ebda aspett bl-isem \"$1\".\nFtakar li l-paġni .css u .js personalizzati għandhom l-ewwel ittra tat-titlu żgħira, eż. {{ns:user}}:Foo/vector.css u mhux {{ns:user}}:Foo/Vector.css.",
        "updated": "(Aġġornata)",
        "note": "'''Nota:'''",
        "previewnote": "'''Ftakar li din hija biss dehra proviżorja.'''\nIt-tibdiliet tiegħek għadhom ma ġewx salvati!",
        "prefs-files": "Fajls",
        "prefs-custom-css": "CSS personalizzat",
        "prefs-custom-js": "JS personalizzat",
-       "prefs-common-css-js": "CSS/JS maqsum għal kull aspett grafiku:",
+       "prefs-common-config": "CSS/JS maqsum għal kull aspett grafiku:",
        "prefs-reset-intro": "Inti tista' tuża' din il-paġna sabiex terġa' tbiddel il-preferenzi tiegħek għal dawk li ngħatawlek fil-bidu. Din l-operazzjoni hija definittiva u ma tistax tiġi mħassra.",
        "prefs-emailconfirm-label": "Konferma tal-ittra-e:",
        "youremail": "E-mail:",
index 8571dbd..d7050d8 100644 (file)
        "prefs-files": "Fexeiros",
        "prefs-custom-css": "CSS personalizada",
        "prefs-custom-js": "JS personalizado",
-       "prefs-common-css-js": "CSS/JS partilhado por todas las maçcarilhas:",
+       "prefs-common-config": "CSS/JS partilhado por todas las maçcarilhas:",
        "prefs-emailconfirm-label": "Cunfirmaçon de l correio eiletrónico:",
        "youremail": "Morada de correio eiletrónico:",
        "username": "Nome de {{GENDER:$1|outelizador|outelizadora|outelizador(a)}}:",
index 66a71d2..ae3e442 100644 (file)
        "customjsprotected": "你無授權去改這个JavaScript頁,因為這个頁包括別个用者的個人設定。",
        "mycustomcssprotected": "Lí bô hí-khó lâi kái chit ê CSS ia̍h.",
        "mycustomjsprotected": "Lí bô hí-khó lâi kái chit ê Javascript ia̍h.",
-       "myprivateinfoprotected": "Lí bô hí-khó lâi kái lí ka-tī--ê su-jîn chu-sìn.",
+       "myprivateinfoprotected": "Lí bô hí-khó lâi kái lí ka-kī--ê su-jîn chu-sìn.",
        "mypreferencesprotected": "Lí bô hí-khó kái ka-kī--ê iōng-chiá siat-tēng.",
        "ns-specialprotected": "特殊頁袂得改。",
        "titleprotected": "這个標題已經予[[User:$1|$1]]保護起來,袂得提來用。\n原因是 <em>$2</em>。",
        "newarticle": "(Sin)",
        "newarticletext": "Lí tòe 1 ê liân-kiat lâi kàu 1 bīn iáu-bōe chûn-chāi ê ia̍h. Beh khai-sí pian-chi̍p chit ia̍h, chhiáⁿ tī ē-kha ê bûn-jī keh-á lāi-té phah-jī. ([$1 Bo̍k-lio̍k] kà lí án-choáⁿ chìn-hêng.) Ká-sú lí bô-tiuⁿ-tî lâi kàu chia, ē-sai chhi̍h liû-lám-khì ê '''téng-1-ia̍h''' tńg--khì.",
        "anontalkpagetext": "----''Pún thó-lūn-ia̍h bô kò·-tēng ê kháu-chō/hō·-thâu, kan-na ū 1 ê IP chū-chí (chhin-chhiūⁿ 123.456.789.123). In-ūi bô kāng lâng tī bô kāng sî-chūn ū khó-lêng tú-hó kong-ke kāng-ê IP, lâu tī chia ê oē ū khó-lêng hō· bô kāng lâng ê! Beh pī-bián chit khoán būn-tê, ē-sái khì [[Special:UserLogin|khui 1 ê hō·-thâu a̍h-sī teng-ji̍p]].''",
-       "noarticletext": "這頁這馬無內容,你會使佇別頁[[Special:Search/{{PAGENAME}}|揣這頁標題]],\n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} 揣相關日誌],\n抑[{{fullurl:{{FULLPAGENAME}}|action=edit}} 創造這頁]</span>。",
+       "noarticletext": "Chit ia̍h chit-má bô loē-iông, lí ē-sái tī pa̍t ia̍h [[Special:Search/{{PAGENAME}}|chhoē chit ia̍h piau-tê]], \n<span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} chhoē siong-koan ji̍t-chì], \niah [{{fullurl:{{FULLPAGENAME}}|action=edit}} chhòng-chò chit ia̍h]</span>。",
        "clearyourcache": "'''Chù-ì:''' Pó-chûn liáu-āu, tio̍h ē-kì leh kā liû-lám-khì ê cache piàⁿ tiāu chiah khoàⁿ-ē-tio̍h kái-piàn: *'''Firefox / Safari:''' chhi̍h tiâu \"Shift\" kâng-sî-chūn tiám-kik ''Reload/têng-sin chài-ji̍p'' a̍h-sī chhi̍h ''Ctrl-F5'' \"Ctrl-R\" kî-tiong chi̍t ê (''Command-R'' tī Mac) \n* '''Google Chrome:''' chhi̍h ''Ctrl-Shift-R'' (''Command-Shift-R'' tī Mac)\n'''Internet Explorer :'''chhi̍h tiâu \"Ctrl\" kâng-sî-chūn tiám-kek ''Refresh/têng-sin chài-ji̍p'' a̍h-sī chhi̍h \"Ctrl-F5\" \n* '''Konqueror:'''  tiám-kek ''Reload/têng-sin chài-ji̍p'' a̍h-sī chhi̍h ''F5''\n* '''Opera:''' piàⁿ-tiāu cache tī ''Tools(ke-si) → Preferences(siat-piān)''",
        "usercssyoucanpreview": "'''Phiat-pō·''': Pó-chûn chìn-chêng ē-sái chhi̍h 'Seng khoàⁿ-māi' kiám-cha sin ê CSS a̍h-sī JavaScript.",
        "userjsyoucanpreview": "'''Phiat-pō·''': Pó-chûn chìn-chêng ē-sái chhi̍h 'Seng khoàⁿ-māi' kiám-cha sin ê CSS a̍h-sī JavaScript.",
        "prefs-files": "Tóng-àn",
        "prefs-custom-css": "Chū-siat CSS",
        "prefs-custom-js": "Chū-siat JavaScript",
-       "prefs-common-css-js": "Só͘-ū gōa-phôe kong-ke ê CSS/JavaScript",
+       "prefs-common-config": "Só͘-ū gōa-phôe kong-ke ê CSS/JavaScript",
        "prefs-reset-intro": "Lí ē-sài ēng pún ia̍h lâi chiong lí ê siat-tì têng-siat chò pún chām kì-tēng.\nChe bē hoat-tō͘ ho̍k-goân.",
        "prefs-emailconfirm-label": "Tiān-chú-phoe khak-tēng:",
        "youremail": "Lí ê email:",
index d8cd8f6..00975fd 100644 (file)
        "userjspreview": "'''Arricuordate ca chest'è sulamente n'anteprimma p' 'o JavaScript perzunale. 'E cagnamiente nun so' state ancora sarvate!'''",
        "sitecsspreview": "'''Arricuordate ca chest'è sulamente n'anteprimma p' 'o CSS. 'E cagnamiente nun so' state ancora sarvate!'''",
        "sitejspreview": "'''Arricuordate ca chest'è sulamente n'anteprimma p' 'o codece JavaScript. 'E cagnamiente nun so' state ancora sarvate!'''",
-       "userinvalidcssjstitle": "'''Attenziò:''' Nun esiste nisciuna skin c' 'o nomme \"$1\". Vide ch' 'e paggene .css e .js personalezzate teneno nu titolo n minucola, p'esempio {{ns:user}}:Esempio/vector.css e nun {{ns:user}}:Esempio/Vector.css.",
+       "userinvalidconfigtitle": "'''Attenziò:''' Nun esiste nisciuna skin c' 'o nomme \"$1\". Vide ch' 'e paggene .css e .js personalezzate teneno nu titolo n minucola, p'esempio {{ns:user}}:Esempio/vector.css e nun {{ns:user}}:Esempio/Vector.css.",
        "updated": "(Agghiurnato)",
        "note": "'''Nota:'''",
        "previewnote": "'''Chesta è sola n'anteprimma; 'e cagnamiénte â paggena nun songo ancora sarvate!'''",
        "prefs-files": "File",
        "prefs-custom-css": "CSS personalizzato",
        "prefs-custom-js": "JavaScript personalizzato",
-       "prefs-common-css-js": "CSS/JavaScript spartuto pe' tutt' 'e skin:",
+       "prefs-common-config": "CSS/JavaScript spartuto pe' tutt' 'e skin:",
        "prefs-reset-intro": "Putisse ausà sta paggena pe' rimpizzà 'e preferenze proprie comme chille predefinite d' 'o sito.\nL'operazione nun se può annullà.",
        "prefs-emailconfirm-label": "Cunferma 'e ll'e-mail:",
        "youremail": "E-mail:",
        "htmlform-user-not-exists": "<strong>$1</strong> nun esiste.",
        "htmlform-user-not-valid": "<strong>$1</strong> nun è nu nomme buono.",
        "logentry-delete-delete": "$1 {{GENDER:$2|scancellaje}} 'a paggena $3",
-       "logentry-delete-restore": "$1 {{GENDER:$2|arrepigliaje}} 'a paggena $3",
+       "logentry-delete-restore": "$1 {{GENDER:$2|arrepigliaje}} 'a paggena $3 ($4)",
        "logentry-delete-event": "$1 {{GENDER:$2|cagnaie}} 'a vesibbiletà 'e {{PLURAL:$5|n'azione d' 'o riggistro|$5 aziune d' 'o riggistro}} ncopp' 'a 'a $3: $4",
        "logentry-delete-revision": "$1 {{GENDER:$2|cagnaie}} 'a vesibbiletà 'e {{PLURAL:$5|na verziona|$5 verziune}} ncopp' 'a 'a $3: $4",
        "logentry-delete-event-legacy": "$1 {{GENDER:$2|cagnaie}} 'a vesibbiletà 'e l'aziune dint' 'o riggistro ncopp' 'a $3: $4",
index 276c072..fe826a2 100644 (file)
        "userjspreview": "'''Husk at dette bare er en test eller forhåndsvisning av ditt bruker-JavaScript, og det ikke er lagret!'''",
        "sitecsspreview": "'''Husk at du bare forhåndsviser denne CSS-en.'''\n'''Den har ikke blitt lagret ennå!'''",
        "sitejspreview": "'''Husk at du bare forhåndsviser denne JavaScript-koden.'''\n'''Den har ikke blitt lagret ennå!'''",
-       "userinvalidcssjstitle": "'''Advarsel:''' Det finnes ikke noe utseende ved navn «$1». Husk at .css- og .js-sider bruker titler i små bokstaver, for eksempel {{ns:user}}:Eksempel/vector.css, ikke {{ns:user}}:Eksempel/Vector.css",
+       "userinvalidconfigtitle": "'''Advarsel:''' Det finnes ikke noe utseende ved navn «$1». Husk at .css- og .js-sider bruker titler i små bokstaver, for eksempel {{ns:user}}:Eksempel/vector.css, ikke {{ns:user}}:Eksempel/Vector.css",
        "updated": "(Oppdatert)",
        "note": "'''Merk:'''",
        "previewnote": "'''Husk at dette bare er en forhåndsvisning.'''\nEndringene dine har ikke blitt lagret ennå!",
        "prefs-files": "Filer",
        "prefs-custom-css": "Personlig CSS",
        "prefs-custom-js": "Personlig Javascript",
-       "prefs-common-css-js": "Delt CSS/JS for alle drakter:",
+       "prefs-common-config": "Delt CSS/JS for alle drakter:",
        "prefs-reset-intro": "Du kan bruke denne siden til å tilbakestille innstillingene dine til standardinnstillingene.\nDette kan ikke tilbakestilles.",
        "prefs-emailconfirm-label": "E-postbekreftelse:",
        "youremail": "E-post:",
        "thumbnail_dest_directory": "Klarte ikke å opprette målmappe",
        "thumbnail_image-type": "Bildetypen støttes ikke",
        "thumbnail_gd-library": "Ufullstendig konfigurering av GD library: mangler funksjonen $1",
+       "thumbnail_image-size-zero": "Filstørrelsen til bildet ser ut til å være null.",
        "thumbnail_image-missing": "Filen ser ut til å mangle: $1",
        "thumbnail_image-failure-limit": "Det har vært for mange nylige forsøk ($1 eller flere) på å gjengi dette miniatyrbildet. Vennligst prøv igjen senere.",
        "import": "Importer sider",
index b621163..7f756ba 100644 (file)
        "userjspreview": "'''Denk deran da'j joew persoonlike JavaScript allinnig nog mer an t bekieken bin, t is nog niet op-esleugen!'''",
        "sitecsspreview": "'''Je bin allinnig mer de CSS an t naokieken.'''\n'''t Is nog niet op-esleugen!'''",
        "sitejspreview": "'''Je bin allinnig mer de JavaScript-kode an t naokieken.'''\n'''t Is nog niet op-esleugen!'''",
-       "userinvalidcssjstitle": "'''Waorschuwing:''' der is gien uutvoering mit de naam \"$1\". Vergeet niet dat joew eigen .css- en .js-ziejen beginnen mit n kleine letter, bv. \"{{ns:user}}:Naam/'''v'''ector\" in plaotse van \"{{ns:user}}:Naam/'''V'''ector.css\".",
+       "userinvalidconfigtitle": "'''Waorschuwing:''' der is gien uutvoering mit de naam \"$1\". Vergeet niet dat joew eigen .css- en .js-ziejen beginnen mit n kleine letter, bv. \"{{ns:user}}:Naam/'''v'''ector\" in plaotse van \"{{ns:user}}:Naam/'''V'''ector.css\".",
        "updated": "(Bewark)",
        "note": "'''Opmarking:'''",
        "previewnote": "'''Waort je: dit is n naokiekzied.'''\nJoew tekste is niet op-esleugen!",
        "prefs-files": "Bestaanden",
        "prefs-custom-css": "Persoonlike CSS",
        "prefs-custom-js": "Persoonlike JS",
-       "prefs-common-css-js": "Edeelden CSS/JS veur elke vormgeving:",
+       "prefs-common-config": "Edeelden CSS/JS veur elke vormgeving:",
        "prefs-reset-intro": "Je kunnen disse zied gebruken um joew veurkeuren naor de standardinstellingen weerumme te zetten.\nDisse haandeling kan niet ongedaonemaakt wörden.",
        "prefs-emailconfirm-label": "Netpostbevestiging:",
        "youremail": "Netpostadres (niet verplicht) *",
index e71366a..49a51cf 100644 (file)
        "userjsyoucanpreview": "'''Tipp:''' Bruuk den Vörschau-Knoop, üm dien nieg JS vör dat Spiekern to testen.",
        "usercsspreview": "'''Denk doran, dat du blots en Vörschau vun dien CSS ankickst, dat is noch nich spiekert!'''",
        "userjspreview": "'''Denk doran, dat du blots en Vörschau vun dien JS ankiekst, dat is noch nich spiekert!'''",
-       "userinvalidcssjstitle": "'''Wohrschau:''' Dat gifft keen Skin „$1“. Denk dor an, dat .css- un .js-Sieden  för Brukers mit en lütten Bookstaven anfangen mööt, to’n Bispeel ''{{ns:user}}:Brukernaam/vector.css'' un nich ''{{ns:user}}:Brukernaam/Vector.css''.",
+       "userinvalidconfigtitle": "'''Wohrschau:''' Dat gifft keen Skin „$1“. Denk dor an, dat .css- un .js-Sieden  för Brukers mit en lütten Bookstaven anfangen mööt, to’n Bispeel ''{{ns:user}}:Brukernaam/vector.css'' un nich ''{{ns:user}}:Brukernaam/Vector.css''.",
        "updated": "(Ännert)",
        "note": "'''Wohrschau:'''",
        "previewnote": "Dit is bloots en Vörschau, de Sied is noch nich spiekert!'''",
        "prefs-files": "Datein",
        "prefs-custom-css": "Anpasst CSS",
        "prefs-custom-js": "Anpasst JS",
-       "prefs-common-css-js": "Deelt CSS/JavaScript för all Skins:",
+       "prefs-common-config": "Deelt CSS/JavaScript för all Skins:",
        "prefs-reset-intro": "Du kannst disse Sied bruken, dien Instellungen al op de Standardinstellung trüchtosetten.\nDat kann nich wedder ungeschehn maakt warrn.",
        "prefs-emailconfirm-label": "E-Mail-Bestätigung:",
        "youremail": "Dien E-Mail (kene Plicht) *",
index 3a53657..ea3d2c4 100644 (file)
        "userjspreview": "<strong>याद राख्नुहोस तपाईंले आफ्नो प्रयोगकर्ता जाभास्क्रिप्टको पूर्वावलोकन मात्र हेरिरहनु भएको छ।\nयसलाइ अहिले सम्म सङ्ग्रह गरिएको छैन!</strong>",
        "sitecsspreview": "<strong>याद राख्नुहोस् तपाईंले केवल विश्वव्यापी सियसयसको पूर्वावलोकन मात्र अवलोकन गर्नुभएको छ ।\nयसलाई अहिलेसम्म सङ्ग्रह गरिएको छैन!</strong>",
        "sitejspreview": "<strong>याद राख्नुहोस् तपाईंले केवल जाभास्क्रिप्ट कोडको पूर्वावलोकन मात्र हेरिरहनु भएको छ।\nयसलाई अहिले सम्म सङ्ग्रह गरिएको छैन!</strong>",
-       "userinvalidcssjstitle": "<strong>चेतावनी:</strong> यहाँ कुनैपनि \"$1\" नामको खोल छैन।\nप्रचलित .css तथा .js पृष्ठहरूले निम्नपद शीर्षक प्रयोग गर्छन्, जस्तै {{ns:user}}:Foo/Vector.css को सट्टामा {{ns:user}}:Foo/vector.css",
+       "userinvalidconfigtitle": "<strong>चेतावनी:</strong> यहाँ कुनैपनि \"$1\" नामको खोल छैन।\nप्रचलित .css तथा .js पृष्ठहरूले निम्नपद शीर्षक प्रयोग गर्छन्, जस्तै {{ns:user}}:Foo/Vector.css को सट्टामा {{ns:user}}:Foo/vector.css",
        "updated": "नवीन",
        "note": "'''सूचना:'''",
        "previewnote": "'''याद राख्नुहोस् यो केवल पूर्वावलोकन मात्र हो; तपाईंका परिवर्तनहरू संग्रहित भएका छैनन्!'''",
        "prefs-files": "फाइलहरू",
        "prefs-custom-css": "अनुकुलित CSS",
        "prefs-custom-js": "अनुकुलित JS",
-       "prefs-common-css-js": "साझा CSS/जाभा स्क्रिप्ट सबै त्वचा(स्किन)को लागि:",
+       "prefs-common-config": "साझा CSS/जाभा स्क्रिप्ट सबै त्वचा(स्किन)को लागि:",
        "prefs-reset-intro": "तपाईं यो पृष्ठलाई आफ्नो अभिरुचीहरू साइट पूर्वावस्थामा फर्काउन प्रयोग गर्न सक्नुहुन्छ । त्यस पछि यसलाई रद्द गर्न सक्नुहुन्न ।",
        "prefs-emailconfirm-label": "इ-मेल एकिन प्रक्रिया :",
        "youremail": "ईमेल",
index a6d0fad..db558b3 100644 (file)
@@ -93,7 +93,7 @@
        "userjsyoucanpreview": "'''Tip:''' gebruik de knop \"{{int:showpreview}}\" om je nieuwe JavaScript te testen alvorens op te slaan.",
        "usercsspreview": "'''Dit is alleen een voorvertoning van je persoonlijke CSS.\nDeze is nog niet opgeslagen!'''",
        "userjspreview": "'''Let op: je test nu je persoonlijke JavaScript.'''\n'''De pagina is niet opgeslagen!'''",
-       "userinvalidcssjstitle": "'''Waarschuwing:''' er is geen uiterlijk \"$1\".\nJe eigen .css- en .js-pagina's beginnen met een kleine letter, bijvoorbeeld {{ns:user}}:Naam/vector.css in plaats van {{ns:user}}:Naam/Vector.css.",
+       "userinvalidconfigtitle": "'''Waarschuwing:''' er is geen uiterlijk \"$1\".\nJe eigen .css- en .js-pagina's beginnen met een kleine letter, bijvoorbeeld {{ns:user}}:Naam/vector.css in plaats van {{ns:user}}:Naam/Vector.css.",
        "previewnote": "'''Let op: dit is een controlepagina.'''\nJe tekst is niet opgeslagen!",
        "previewconflict": "Deze voorvertoning geeft aan hoe de tekst in het bovenste veld eruit ziet als je deze opslaat.",
        "session_fail_preview": "'''Je bewerking is niet verwerkt, omdat de sessiegegevens verloren zijn gegaan.\nProbeer het opnieuw.\nAls het dan nog niet lukt, [[Special:UserLogout|meld jezelf dan af]] en weer aan.'''",
index 48620c4..db1959d 100644 (file)
        "userjspreview": "'''Let op: u test nu uw persoonlijke JavaScript.'''\n'''De pagina is niet opgeslagen!'''",
        "sitecsspreview": "'''Dit is alleen een voorvertoning van de CSS.'''\n'''Deze is nog niet opgeslagen!'''",
        "sitejspreview": "'''Dit is alleen een voorvertoning van de JavaScriptcode.'''\n'''Deze is nog niet opgeslagen!'''",
-       "userinvalidcssjstitle": "'''Waarschuwing:''' er is geen uiterlijk \"$1\".\nUw eigen .css- en .js-pagina's beginnen met een kleine letter, bijvoorbeeld {{ns:user}}:Naam/vector.css in plaats van {{ns:user}}:Naam/Vector.css.",
+       "userinvalidconfigtitle": "'''Waarschuwing:''' er is geen uiterlijk \"$1\".\nUw eigen .css- en .js-pagina's beginnen met een kleine letter, bijvoorbeeld {{ns:user}}:Naam/vector.css in plaats van {{ns:user}}:Naam/Vector.css.",
        "updated": "(Bijgewerkt)",
        "note": "<strong>Opmerking:</strong>",
        "previewnote": "'''Let op: dit is een controlepagina.'''\nUw tekst is niet opgeslagen!",
        "prefs-files": "Bestanden",
        "prefs-custom-css": "Aangepaste CSS",
        "prefs-custom-js": "Aangepast JavaScript",
-       "prefs-common-css-js": "Gedeelde CSS/JavaScript voor elke vormgeving:",
+       "prefs-common-config": "Gedeelde CSS/JavaScript voor elke vormgeving:",
        "prefs-reset-intro": "Gebruik deze functie om uw voorkeuren te herstellen naar de standaardinstellingen.\nDeze handeling kan niet ongedaan gemaakt worden.",
        "prefs-emailconfirm-label": "E-mailbevestiging:",
        "youremail": "E-mailadres:",
        "thumbnail_dest_directory": "Niet in staat doelmap aan te maken",
        "thumbnail_image-type": "Dit bestandstype wordt niet ondersteund",
        "thumbnail_gd-library": "De instellingen voor de GD-bibliotheek zijn incompleet. De functie $1 ontbreekt",
+       "thumbnail_image-size-zero": "De afbeeldingsbestandsgrootte lijkt nul te zijn.",
        "thumbnail_image-missing": "Het bestand lijkt niet aanwezig te zijn: $1",
        "thumbnail_image-failure-limit": "Het maken van een miniatuurafbeelding is te vaak mislukt ($1 keer of vaker). Probeer het later nog eens.",
        "import": "Pagina's importeren",
index ed3f733..a4a0b18 100644 (file)
        "userjspreview": "'''Hugs at du berre testar eller førehandsviser brukar-JavaScript-et ditt. Det har ikkje vorte lagra enno!'''",
        "sitecsspreview": "'''Hugs at du berre førehandsviser dette stilarket. '''\n'''Det er ikkje lagra enno!'''",
        "sitejspreview": "'''Hugs at du berre førehandsviser denne JavaScript-koden.'''\n'''Han er ikkje lagra enno!'''",
-       "userinvalidcssjstitle": "'''Åtvaring:''' Det finst ikkje noka sidedrakt som heiter «$1». Hugs på at vanlege .css- og .js-sider brukar titlar med små bokstavar, til dømes {{ns:user}}:Døme/vector.css, og ikkje {{ns:user}}:Døme/Vector.css.",
+       "userinvalidconfigtitle": "'''Åtvaring:''' Det finst ikkje noka sidedrakt som heiter «$1». Hugs på at vanlege .css- og .js-sider brukar titlar med små bokstavar, til dømes {{ns:user}}:Døme/vector.css, og ikkje {{ns:user}}:Døme/Vector.css.",
        "updated": "(Oppdatert)",
        "note": "'''Merk:'''",
        "previewnote": "'''Hugs at dette berre er ei førehandsvising.'''\nEndringane dine er ikkje lagra enno!",
        "titlematches": "Sidetitlar med treff på førespurnaden",
        "textmatches": "Sider med treff på førespurnaden",
        "notextmatches": "Ingen sider hadde treff på førespurnaden",
-       "prevn": "førre {{PLURAL:$1|$1}}",
+       "prevn": "{{PLURAL:$1|førre|førre $1}}",
        "nextn": "{{PLURAL:$1|neste|neste $1}}",
        "prev-page": "førre sida",
        "next-page": "neste side",
        "prefs-files": "Filer",
        "prefs-custom-css": "Eigendefinert CSS",
        "prefs-custom-js": "Eigendefinert JavaScript",
-       "prefs-common-css-js": "Delt CSS/JavaScript for alle draktene:",
+       "prefs-common-config": "Delt CSS/JavaScript for alle draktene:",
        "prefs-reset-intro": "Du kan nytta denne sida til å tilbakestilla innstillingane dine til standardinnstillingane.\nDette kan ikkje tilbakestillast.",
        "prefs-emailconfirm-label": "Stadfesting av e-post:",
        "youremail": "E-post:",
index 6d4e26d..368623e 100644 (file)
        "userjspreview": "'''Remembratz-vos que sètz a visualizar o testar vòstre còdi JavaScript e qu’es pas encara estat enregistrat !'''",
        "sitecsspreview": "'''Remembratz-vos que sètz a previsualizar vòstre pròpri fuèlh CSS !'''\n'''Es pas estada encara enregistrada !'''",
        "sitejspreview": "'''Remembratz-vos que sètz a visualizar o testar vòstre còdi JavaScript e qu’es pas encara estat enregistrat !'''",
-       "userinvalidcssjstitle": "'''Atencion :''' existís pas d'estil « $1 ». Remembratz-vos que las paginas personalas amb extensions .css e .js utilizan de títols en minusculas, per exemple, {{ns:user}}:Foo/vector.css e non pas {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Atencion :''' existís pas d'estil « $1 ». Remembratz-vos que las paginas personalas amb extensions .css e .js utilizan de títols en minusculas, per exemple, {{ns:user}}:Foo/vector.css e non pas {{ns:user}}:Foo/Vector.css.",
        "updated": "(Mes a jorn)",
        "note": "'''Nòta :'''",
        "previewnote": "'''Remembratz-vos qu'es pas qu'una previsualizacion.'''\nVòstras modificacions son pas encara estadas enregistradas !",
        "prefs-files": "Fichièrs",
        "prefs-custom-css": "CSS personalizat",
        "prefs-custom-js": "JS personalizat",
-       "prefs-common-css-js": "JavaScript e CSS partejat per totes los abilhatges :",
+       "prefs-common-config": "JavaScript e CSS partejat per totes los abilhatges :",
        "prefs-reset-intro": "Podètz utilizar aquesta pagina per restablir vòstras preferéncias a las valors per defaut del site. Aquò pòt pas èsser desfait.",
        "prefs-emailconfirm-label": "Confirmacion del corrièr electronic :",
        "youremail": "Adreça de corrièr electronic :",
index 3fbf2c4..3d50f90 100644 (file)
        "userjspreview": "'''ଜାଣି ରଖନ୍ତୁ ଯେ ଆପଣ କେବଳ ନିଜର ସଭ୍ୟ ଜାଭାସ୍କ୍ରିପ୍ଟ (JavaScript) ଦେଖୁଅଛନ୍ତି ।'''\n'''ଏହା ଏଯାଏଁ ସାଇତା ଯାଇନାହିଁ!'''",
        "sitecsspreview": "'''ଜାଣି ରଖନ୍ତୁ ଯେ ଆପଣ କେବଳ ଏହି CSS ଦେଖୁଅଛନ୍ତି ।'''\n'''ଏହା ଏଯାଏଁ ସାଇତାଯାଇନାହିଁ!'''",
        "sitejspreview": "'''ଜାଣି ରଖନ୍ତୁ ଯେ ଆପଣ କେବଳ ଏହି ଜାଭାସ୍କ୍ରିପ୍ଟ (JavaScript) ଦେଖୁଅଛନ୍ତି ।'''\n'''ଏହା ଏଯାଏଁ ସାଇତା ଯାଇନାହିଁ!'''",
-       "userinvalidcssjstitle": "'''ଚେତାବନୀ:''' \"$1\" ନାମରେ କୌଣସି ବି ଆବରଣ ନାହିଁ ।\nମନମୁତାବକ .css ଓ .js ପୃଷ୍ଠା ଏକ ଛୋଟ ଇଂରାଜୀ ଅକ୍ଷର ଥିବା ନାମ ନେଇଥାନ୍ତି, ଯଥା: {{ns:user}}:Foo/Vector.css ବଦଳରେ {{ns:user}}:Foo/vector.css ର ବ୍ୟବହାର ।",
+       "userinvalidconfigtitle": "'''ଚେତାବନୀ:''' \"$1\" ନାମରେ କୌଣସି ବି ଆବରଣ ନାହିଁ ।\nମନମୁତାବକ .css ଓ .js ପୃଷ୍ଠା ଏକ ଛୋଟ ଇଂରାଜୀ ଅକ୍ଷର ଥିବା ନାମ ନେଇଥାନ୍ତି, ଯଥା: {{ns:user}}:Foo/Vector.css ବଦଳରେ {{ns:user}}:Foo/vector.css ର ବ୍ୟବହାର ।",
        "updated": "(ସତେଜ କରିଦିଆଗଲା)",
        "note": "'''ଟୀକା:'''",
        "previewnote": "'''ଜାଣିରଖନ୍ତୁ ଯେ, ଏହା କେବଳ ଏକ ଦେଖଣା ।'''\nଆପଣ କରିଥିବା ବଦଳସବୁ ଏଯାଏଁ ସାଇତା ଯାଇନାହିଁ!",
        "prefs-files": "ଫାଇଲ",
        "prefs-custom-css": "ମନମୁତାବକ CSS",
        "prefs-custom-js": "ମନମୁତାବକ JavaScript",
-       "prefs-common-css-js": "ସବୁ ଆବରଣ ପାଇଁ ବଣ୍ଟା ହୋଇଥିବା CSS/JavaScript:",
+       "prefs-common-config": "ସବୁ ଆବରଣ ପାଇଁ ବଣ୍ଟା ହୋଇଥିବା CSS/JavaScript:",
        "prefs-reset-intro": "ଆପଣ ଏହି ପୃଷ୍ଠାଟି ବ୍ୟବହାର କରି ଆପଣା ପସନ୍ଦସବୁକୁ ସାଇଟର ଆରମ୍ଭରେ ଥିବା ସଜାଣିକୁ ଲେଉଟାଇଦେଇପାରିବେ ।\nଏହାକୁ ପଛକୁ ଫେରାଯାଇପାରିବ ନାହିଁ",
        "prefs-emailconfirm-label": "ଇ-ମେଲ ସଜାଣି:",
        "youremail": "ଇ-ମେଲ:",
index f30e77c..554b2b4 100644 (file)
        "userjspreview": "'''Дæ зæрдыл дар уый, æмæ дæ JavaScript ды ныртæккæ фæлваргæ кæныс.'''\n'''Нырмæ æвæрд нæу!'''",
        "sitecsspreview": "'''Дæ зæрдыл дар уый, æмæ ацы CSS ды ныртæккæ фæлваргæ кæныс.'''\n'''Нырмæ æвæрд нæу!'''",
        "sitejspreview": "'''Дæ зæрдыл дар уый, æмæ ацы JavaScript ды ныртæккæ фæлваргæ кæныс.'''\n'''Нырмæ æвæрд нæу!'''",
-       "userinvalidcssjstitle": "'''Сындæг:''' \"$1\" царм нæй.\nХиæвæрд .css æмæ .js фæрстæ архайынц гыццыл дамгъæтимæ нæмттæй. Цæвиддон, {{ns:user}}:Foo/vector.cs, {{ns:user}}:Foo/Vector.css нæ фæлæ.",
+       "userinvalidconfigtitle": "'''Сындæг:''' \"$1\" царм нæй.\nХиæвæрд .css æмæ .js фæрстæ архайынц гыццыл дамгъæтимæ нæмттæй. Цæвиддон, {{ns:user}}:Foo/vector.cs, {{ns:user}}:Foo/Vector.css нæ фæлæ.",
        "updated": "(Ноггонд)",
        "note": "'''Фиппаинаг:'''",
        "previewnote": "'''Зон æй, æмæ ай у æрмæстдæр разбакаст.'''\nДæ ивдтытæ нырмæ æвæрд не рцыдысты!",
index 2460fdc..4ee5614 100644 (file)
        "prefs-files": "ਫ਼ਾਈਲਾਂ",
        "prefs-custom-css": "ਰਿਵਾਇਤੀ CSS",
        "prefs-custom-js": "ਰਿਵਾਇਤੀ ਜਾਵਾਸਕਰਿਪਟ",
-       "prefs-common-css-js": "ਸਾਰੀਆਂ ਸਕਿਨਾਂ ਲਈ ਸਾਂਝਾ CSS/ਜਾਵਾਸਕਰਿਪਟ:",
+       "prefs-common-config": "ਸਾਰੀਆਂ ਸਕਿਨਾਂ ਲਈ ਸਾਂਝਾ CSS/ਜਾਵਾਸਕਰਿਪਟ:",
        "prefs-emailconfirm-label": "ਈ-ਮੇਲ ਪੁਸ਼ਟੀ:",
        "youremail": "ਈ-ਮੇਲ:",
        "username": "{{GENDER:$1|ਵਰਤੋਂਕਾਰ ਦਾ ਨਾਂ}}:",
index 21f57d3..e8d4495 100644 (file)
        "userjsyoucanpreview": "'''Tip:''' Gamitan me ing button a 'Pakit ya ing preview' ('Show preview') ba yang subukan ing kekang bayung JS bayu ka mag-save.",
        "usercsspreview": "'''Tandanan mung pi-preview me mu ing kekang user CSS, e ya pa me-save!'''",
        "userjspreview": "'''Tandanan mung susubukan/pi-preview me pamu ing kekang user JavaScript, e ya pa me-save iti!'''",
-       "userinvalidcssjstitle": "'''Kapiadian:''' Alang pabalat (skin) a \"$1\".\nTandanan mung deng pasadiang bulung (custom pages) a .css ampong .js, gagamit lang bansag a mababang letra (lowercase), alm. (alimbawa), {{ns:user}}:Foo/vector.css, at e {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Kapiadian:''' Alang pabalat (skin) a \"$1\".\nTandanan mung deng pasadiang bulung (custom pages) a .css ampong .js, gagamit lang bansag a mababang letra (lowercase), alm. (alimbawa), {{ns:user}}:Foo/vector.css, at e {{ns:user}}:Foo/Vector.css.",
        "updated": "(Mibayu)",
        "note": "'''Kapabaluan:'''",
        "previewnote": "'''Tandanan mu pasinag ya mu ini.\nDeng elilan mu ela pa misikap!'''",
index b6bff28..5afa598 100644 (file)
        "userjspreview": "'''Pamiętaj, że to tylko podgląd Twojego kodu JavaScript – nic jeszcze nie zostało zapisane!'''",
        "sitecsspreview": "'''Pamiętaj, że to tylko podgląd arkusza stylów CSS.'''\n'''Zmiany nie zostały jeszcze zapisane!'''",
        "sitejspreview": "'''Pamiętaj, że to tylko podgląd kodu JavaScript.'''\n'''Zmiany nie zostały jeszcze zapisane!'''",
-       "userinvalidcssjstitle": "'''Uwaga:''' Brak skórki o nazwie „$1”.\nStrony użytkownika zawierające CSS i JavaScript powinny zaczynać się małą literą, np. {{ns:user}}:Foo/vector.css, w przeciwieństwie do nieprawidłowego {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Uwaga:''' Brak skórki o nazwie „$1”.\nStrony użytkownika zawierające CSS i JavaScript powinny zaczynać się małą literą, np. {{ns:user}}:Foo/vector.css, w przeciwieństwie do nieprawidłowego {{ns:user}}:Foo/Vector.css.",
        "updated": "(Zmodyfikowano)",
        "note": "'''Uwaga:'''",
        "previewnote": "<strong>To jest tylko podgląd.</strong>\nZmiany nie zostały jeszcze zapisane!",
        "prefs-files": "Pliki",
        "prefs-custom-css": "własny CSS",
        "prefs-custom-js": "własny JavaScript",
-       "prefs-common-css-js": "Wspólny CSS/JS dla wszystkich skórek:",
+       "prefs-common-config": "Wspólny CSS/JS dla wszystkich skórek:",
        "prefs-reset-intro": "Na tej stronie można przywrócić domyślne ustawienia preferencji dla tej witryny.\nTej operacji nie można później cofnąć.",
        "prefs-emailconfirm-label": "Potwierdzenie adresu e‐mail:",
        "youremail": "Twój adres e‐mail:",
index 9644648..3543a66 100644 (file)
        "userjspreview": "'''Che as visa che a l'é mach antramentre che as fa na preuva ëd sò còdes Javascript e che a l'é ancó pa stàit salvà!'''",
        "sitecsspreview": "'''Che a varda che a l'é mach an camin ch'a preuva cost CSS.'''\n'''A l'é pa ancora stàit salvà!'''",
        "sitejspreview": "'''Che a varda che a l'é mach an camin ch'a preuva cost còdes JavaScript.'''\n'''A l'é pa ancora stàit salvà!'''",
-       "userinvalidcssjstitle": "'''Atension:''' A-i é gnun-a pel «$1». Che as visa che le pàgine .css e .js che un as fa daspërchiel a deuvro tute minùscole për tìtol, pr'esempi {{ns:user}}:Scaramacaj/vector.css nopà che {{ns:user}}:Scaramacaj/Vector.css.",
+       "userinvalidconfigtitle": "'''Atension:''' A-i é gnun-a pel «$1». Che as visa che le pàgine .css e .js che un as fa daspërchiel a deuvro tute minùscole për tìtol, pr'esempi {{ns:user}}:Scaramacaj/vector.css nopà che {{ns:user}}:Scaramacaj/Vector.css.",
        "updated": "(Agiornà)",
        "note": "'''Nòta:'''",
        "previewnote": "'''Che a ten-a da ment che costa-sì a l'é mach na preuva.'''\nSoe modìfiche a son pa ancora stàite salvà!",
        "prefs-files": "Archivi",
        "prefs-custom-css": "CSS përsonaj",
        "prefs-custom-js": "JS përsonaj",
-       "prefs-common-css-js": "CSS e JS condividù për tute le pej:",
+       "prefs-common-config": "CSS e JS condividù për tute le pej:",
        "prefs-reset-intro": "A peul dovré costa pàgina për amposté torna ij sò gust a coj dë stàndard.\nSòn a peul pa esse anulà.",
        "prefs-emailconfirm-label": "Conferma dl'adrëssa ëd pòsta eletrònica:",
        "youremail": "Soa adrëssa ëd pòsta eletrònica:",
index 985ec41..26f15f2 100644 (file)
        "userjspreview": "'''یاد رکھو بے  تسی صرف اپنی ورتن 'JavaScript چیک کررۓ او''\n'''اینوں ہجے بچایا نئیں گیا!'''",
        "sitecsspreview": "'''یادرکھو جے تسی اپنی ورتن  CSS دا کچا کم ویکھ رۓ او.'''\n'''Iاے ہلے بچائی نئیں گئ!'''",
        "sitejspreview": "'''یاد رکھو بے  تسی صرف ایہ 'JavaScript کوڈ چیک کررۓ او''\n'''اینوں ہجے بچایا نئیں گیا!'''",
-       "userinvalidcssjstitle": "'''خبردار:''' \"$1\" سکن نئیں اے۔\nCustom .css تے .js pages use a lowercase title, e.g. {{ns:user}}:Foo/vector.css as opposed to {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''خبردار:''' \"$1\" سکن نئیں اے۔\nCustom .css تے .js pages use a lowercase title, e.g. {{ns:user}}:Foo/vector.css as opposed to {{ns:user}}:Foo/Vector.css.",
        "updated": "(نواں کیتا گیا)",
        "note": "'''نوٹ:'''",
        "previewnote": "'''اے ہلے کچا کم اے؛ تبدیلیاں بچائیاں نہیں گئیاں'''",
        "prefs-files": "فائلاں",
        "prefs-custom-css": "کسٹم سی ایس ایس",
        "prefs-custom-js": "کسٹم جاواسکرپٹ",
-       "prefs-common-css-js": "سی ایس ایس/جاواسکرپٹ شئیر کرو ہر وکھالے لئی:",
+       "prefs-common-config": "سی ایس ایس/جاواسکرپٹ شئیر کرو ہر وکھالے لئی:",
        "prefs-reset-intro": "تسیں ایس صفے نوں کسے سائٹ دی ڈیفالٹ دی چنوتیاں مرضی دیاں کرن ورت سکدے او۔\n\nاے واپس نئیں ہوسکدا۔",
        "prefs-emailconfirm-label": "ای-میل کنفرمیشن:",
        "youremail": "ای میل:",
index f1e170f..42256e0 100644 (file)
        "clearyourcache": "'''Nōda: Kitawīdinsnas pa enpeisāsnan stēisan nāunan ensadīnsenin mazzi ni būtwei widāminan. Prawerru skistīntun lasāltas rānkas minīsnan.'''\n*'''Mozilla, Firefox anga Safari - ''' zabāis \"Shift\" gnestan ne gnetteis \"Etnauninnais\" anga gnetteis \"Ctrl-F5\" anga \"Ctrl-R\" (\"Command-R\" en Macu);\n*'''Konqueror:''' gnetteis '''Etwārtai kraūneis\" anga \"F5\";\n'''Opera:''' skistinnais rānkas minīsnan en \"Ēnrankis-Pirminiskwas\";\n'''Internet Explorer:''' zabāis \"Ctrl\" gnetīntei \"Etnauninnais\" anga gnetteis \"Ctrl-F5\".",
        "usercsspreview": "'''Pamēnais, kāi sta ast tēr twāise CSS pirmādira - nika dabber ni pastāi enpeisātan!'''",
        "userjspreview": "'''Pamēnais, kāi sta ast tēr twāise JS kōdas pirmādira - nika dabber ni pastāi enpeisātan!'''",
-       "userinvalidcssjstitle": "'''Ēmpirsergīsenis:''' Ni ast prusna \"$1\".\nPamēnais kāi tērpautajas .css be .js pāusai turri pagaūtun si sen līkutan litteran, p. e.g. {{ns:user}}:Foo/vector.css, ni {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Ēmpirsergīsenis:''' Ni ast prusna \"$1\".\nPamēnais kāi tērpautajas .css be .js pāusai turri pagaūtun si sen līkutan litteran, p. e.g. {{ns:user}}:Foo/vector.css, ni {{ns:user}}:Foo/Vector.css.",
        "updated": "(Ernaunīntan)",
        "note": "'''Endirīsenis:'''",
        "previewnote": "'''Sta ast tēr pirmādira.'''\nKitawīdinsnas dabber ni pastāi enpeisātan!\"",
index a6c4a99..e31095a 100644 (file)
        "userjspreview": "'''هېر مو نشي چې دا يوازې ستاسې د کارن د جاوا سکرېپټ آزمېيل/مخليدنه ده.'''\n'''تر اوسه پورې لا ستاسې بدلونونه نه دي خوندي شوي!'''",
        "sitecsspreview": "'''په پام کې دې وي چې دا يوازې ستاسې د CSS مخليدنه ده.'''\n'''تر اوسه پورې لا ستاسې بدلونونه نه دي خوندي شوي!'''",
        "sitejspreview": "'''په پام کې مو اوسه چې تاسې يوازې د دغه جاواسکرېپټ کوډ مخليدنه کوۍ.'''\n'''تر اوسه پورې دا نه دی خوندي شوی!'''",
-       "userinvalidcssjstitle": "<strong>خبرداری:</strong>دلته  هیڅ پوست نشته \"$1\".\nد ګمرکونو .ثي اس اس او .ج س مخونه  کوچني سرلیک استعمالوي، او داسې نور. {{ns:user}}:Foo/vector.css  که مخالف وي نو {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>خبرداری:</strong>دلته  هیڅ پوست نشته \"$1\".\nد ګمرکونو .ثي اس اس او .ج س مخونه  کوچني سرلیک استعمالوي، او داسې نور. {{ns:user}}:Foo/vector.css  که مخالف وي نو {{ns:user}}:Foo/Vector.css.",
        "updated": "(تازه)",
        "note": "'''يادونه:'''",
        "previewnote": "'''هېر مو نه شي چې دا يواځې يوه مخليدنه ده.'''\nستاسې لخوا ترسره شوي بدلونونه لا تر اوسه پورې نه دي خوندي شوي!!",
        "prefs-files": "دوتنې",
        "prefs-custom-css": "دوديزه سي اس اس",
        "prefs-custom-js": "ځاني جاواسکرېپټ",
-       "prefs-common-css-js": "د ټولو پوښونو لپاره د CSS/جاواسکرېپټ دوتنه:",
+       "prefs-common-config": "د ټولو پوښونو لپاره د CSS/جاواسکرېپټ دوتنه:",
        "prefs-emailconfirm-label": "د برېښليک باورتيا:",
        "youremail": "برېښليک *",
        "username": "{{GENDER:$1|کارن نوم}}:",
        "siteuser": "د {{SITENAME}} کارن $1",
        "anonuser": "د {{SITENAME}} ورکنومی کارن $1",
        "lastmodifiedatby": "دا مخ وروستی ځل $3 لخوا په $2، $1 بدلون موندلی.",
-       "othercontribs": "Ù\86Ù\88ر Ú©Ø§Ø± Ù¾Ø± Ø¨Ù\86سټ",
+       "othercontribs": "Ù\86Ù\88ر Ú©Ø§Ø± Ù¾Ø± Ø§Ø³Ø§Ø³ Ø¯ $1.",
        "others": "نور",
        "siteusers": "د {{SITENAME}} {{PLURAL:$2|کارن|کارنان}} $1",
        "anonusers": "د {{SITENAME}} {{PLURAL:$2|ورکنومی کارن|ورکنومي کارنان}} $1",
index d75932d..a9b5f9a 100644 (file)
        "userjspreview": "'''Lembre-se que está apenas testando/prevendo o seu JavaScript particular e que ele ainda não foi salvo!'''",
        "sitecsspreview": "'''Lembre-se de que você está apenas previsualizando este CSS.'''\n'''Ele ainda não foi salvo!'''",
        "sitejspreview": "'''Lembre-se de que você está apenas previsualizando este código JavaScript.'''\n'''Ele ainda não foi salvo!'''",
-       "userinvalidcssjstitle": "'''Aviso:''' Não existe um tema \"$1\". Lembre-se que as páginas .css e  .js utilizam um título em minúsculas, exemplo: {{ns:user}}:Alguém/vector.css aposto a {{ns:user}}:Alguém/Vector.css.",
+       "userinvalidconfigtitle": "'''Aviso:''' Não existe um tema \"$1\". Lembre-se que as páginas .css e  .js utilizam um título em minúsculas, exemplo: {{ns:user}}:Alguém/vector.css aposto a {{ns:user}}:Alguém/Vector.css.",
        "updated": "(Atualizado)",
        "note": "'''Nota:'''",
        "previewnote": "'''Lembre-se de que isto é apenas uma previsão.'''\nSuas alterações ainda não foram salvas!",
        "prefs-files": "Arquivos",
        "prefs-custom-css": "CSS personalizada",
        "prefs-custom-js": "JS personalizado",
-       "prefs-common-css-js": "CSS/JS compartilhado por todos os temas:",
+       "prefs-common-config": "CSS/JS compartilhado por todos os temas:",
        "prefs-reset-intro": "Você pode usar esta página para restaurar as suas preferências para os valores predefinidos do sítio.\nEsta ação não pode ser desfeita.",
        "prefs-emailconfirm-label": "Confirmação do e-mail:",
        "youremail": "Seu e-mail:",
index 8a34f80..b52587a 100644 (file)
        "userjspreview": "<strong>Lembre-se de que está apenas a testar ou a antever o seu JavaScript particular.\nEste ainda não foi gravado!</strong>",
        "sitecsspreview": "<strong>Lembre-se de que está apenas a antever este CSS.\nEle ainda não foi gravado!</strong>",
        "sitejspreview": "<strong>Lembre-se de que está apenas a antever este código JavaScript.\nEle ainda não foi gravado!</strong>",
-       "userinvalidcssjstitle": "<strong>Aviso:</strong> Não existe um tema \"$1\".\nAs páginas personalizadas .css e .js têm um título em minúsculas, por exemplo: {{ns:user}}:Alguém/vector.css em vez de {{ns:user}}:Alguém/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Aviso:</strong> Não existe um tema \"$1\".\nAs páginas personalizadas .css e .js têm um título em minúsculas, por exemplo: {{ns:user}}:Alguém/vector.css em vez de {{ns:user}}:Alguém/Vector.css.",
        "updated": "(Atualizado)",
        "note": "<strong>Nota:</strong>",
        "previewnote": "<strong>Lembre-se de que esta é apenas uma antevisão do resultado.</strong>\nAs modificações ainda não foram gravadas!",
        "prefs-files": "Ficheiros",
        "prefs-custom-css": "CSS personalizado",
        "prefs-custom-js": "JS personalizado",
-       "prefs-common-css-js": "CSS/JS partilhado por todos os temas:",
+       "prefs-common-config": "CSS/JS partilhado por todos os temas:",
        "prefs-reset-intro": "Pode usar esta página para repor as configurações padrão das preferências.\nAs suas preferências serão modificadas para os valores predefinidos do sítio.\nEsta operação não pode ser desfeita.",
        "prefs-emailconfirm-label": "Confirmação do correio eletrónico:",
        "youremail": "Correio eletrónico:",
index edf5b8f..8b3db5d 100644 (file)
        "userjspreview": "'''Yuyariy, qhawarillachkankim ruraqpa JavaScript-niykita, manaraqmi waqaychasqachu!'''",
        "sitecsspreview": "'''Yuyariy, qhawarillachkankim kay CSS-ta.'''\n'''Manaraqmi waqaychasqachu!'''",
        "sitejspreview": "'''Yuyariy, qhawarillachkankim kay JavaScript qillqata.'''\n'''Manaraqmi waqaychasqachu!'''",
-       "userinvalidcssjstitle": "'''Paqtataq:''' Manam kanchu \"$1\" qara. Yuyariy, kikinpa .css, .js p'anqankunaqa uchuy sanampa umalliyuqmi, ahinataq {{ns:user}}:Foo/vector.css manataq  {{ns:user}}:Foo/Vector.css nisqachu.",
+       "userinvalidconfigtitle": "'''Paqtataq:''' Manam kanchu \"$1\" qara. Yuyariy, kikinpa .css, .js p'anqankunaqa uchuy sanampa umalliyuqmi, ahinataq {{ns:user}}:Foo/vector.css manataq  {{ns:user}}:Foo/Vector.css nisqachu.",
        "updated": "(Musuqchasqa)",
        "note": "'''Musyay:'''",
        "previewnote": "'''Yuyaykuy: Kayqa qhawariyllam.'''\nLlamk'apusqaykiqa manaraqmi waqaychasqachu!",
        "prefs-files": "Willañiqikuna",
        "prefs-custom-css": "Munakusqa CSS",
        "prefs-custom-js": "Munakusqa JS",
-       "prefs-common-css-js": "Tukuy qarakunapaq rakinakusqa CSS/JS:",
+       "prefs-common-config": "Tukuy qarakunapaq rakinakusqa CSS/JS:",
        "prefs-reset-intro": "Kay p'anqataqa llamk'achiyta atinki allinkachinaykikunata kikinmanta kasqaman kutichinaykipaq.\nChaytataq manam kutichiyta atinkichu.",
        "prefs-emailconfirm-label": "E-chaskita takyachiy:",
        "youremail": "E-chaski imamaytayki",
index fa48180..b4a9057 100644 (file)
        "userjspreview": "'''Fa stim che quai è be ina prevista da tes JavaScript d'utilisader.'''\n'''El n'è anc betg memorisà.'''",
        "sitecsspreview": "'''Fa stim che quai è be ina prevista da quest CSS.'''\n'''El n'è anc betg memorisà.'''",
        "sitejspreview": "'''Fa stim che quai è be ina prevista da quest JavaScript.'''\n'''El n'è anc betg memorisà.'''",
-       "userinvalidcssjstitle": "'''Attenziun:''' I n'exista nagin skin \"$1\".\nFa stim che titels da paginas persunalisadas .css u .js vegnan scrits pitschen, p. ex. {{ns:user}}:Foo/vector.css e betg {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Attenziun:''' I n'exista nagin skin \"$1\".\nFa stim che titels da paginas persunalisadas .css u .js vegnan scrits pitschen, p. ex. {{ns:user}}:Foo/vector.css e betg {{ns:user}}:Foo/Vector.css.",
        "updated": "(midà)",
        "note": "'''Remartga:'''",
        "previewnote": "'''Fa stim che quai è be ina prevista.'''\nTias midadas n'èn anc betg vegnidas memorisadas!",
        "prefs-files": "Datotecas",
        "prefs-custom-css": "CSS persunalisà",
        "prefs-custom-js": "JavaScript persunalisà",
-       "prefs-common-css-js": "CSS/JavaScript cundividì per tut ils skins:",
+       "prefs-common-config": "CSS/JavaScript cundividì per tut ils skins:",
        "prefs-reset-intro": "Ti pos utilisar questa pagina per restituir las valurs da standard da questa pagina per tias preferenzas. \nQuesta operaziun na po betg vegnir revocada.",
        "prefs-emailconfirm-label": "Confirmaziun per e-mail:",
        "youremail": "Adressa dad e-mail:",
index 2386dc4..dc4ab2d 100644 (file)
        "userjspreview": "'''Rețineți că vizualizați doar o previzualizare/versiune de testare a JavaScript-ului dumneavoastră de utilizator.'''\n'''Acesta nu a fost încă salvat!'''",
        "sitecsspreview": "'''Rețineți că doar previzualizați această foaie de stil.'''\n'''Ea nu a fost salvată încă!'''",
        "sitejspreview": "'''Rețineți că doar previzualizați acest cod JavaScript.'''\n'''El nu a fost salvat încă!'''",
-       "userinvalidcssjstitle": "'''Avertizare:''' Nu există aspectul „$1”.\nPaginile .css și .js specifice utilizatorilor au titluri care încep cu literă mică; de exemplu {{ns:user}}:Foo/vector.css în comparație cu {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Avertizare:''' Nu există aspectul „$1”.\nPaginile .css și .js specifice utilizatorilor au titluri care încep cu literă mică; de exemplu {{ns:user}}:Foo/vector.css în comparație cu {{ns:user}}:Foo/Vector.css.",
        "updated": "(Actualizat)",
        "note": "'''Notă:'''",
        "previewnote": "'''Țineți cont că aceasta este doar o previzualizare.'''\nModificările dumneavoastră nu au fost încă salvate!",
        "prefs-files": "Fișiere",
        "prefs-custom-css": "CSS personalizat",
        "prefs-custom-js": "JS personalizat",
-       "prefs-common-css-js": "Pagini CSS și JavaScript comune pentru toate interfețele:",
+       "prefs-common-config": "Pagini CSS și JavaScript comune pentru toate interfețele:",
        "prefs-reset-intro": "Poți folosi această pagină pentru a reseta preferințele la valorile implicite.\nAcțiunea nu este reversibilă.",
        "prefs-emailconfirm-label": "Confirmare e-mail:",
        "youremail": "Adresă de e-mail:",
index 640c040..03956b5 100644 (file)
        "userjspreview": "'''Arrecuerdete ca tu ste vide/teste sulamende in andeprime 'u JavaScript tue.'''\n'''Non g'à state angore reggistrete ninde!'''",
        "sitecsspreview": "'''Arrecuerdete ca tu ste vide sulamende in andeprime 'u CSS tune.'''\n'''Non g'à state angore reggistrate ninde!'''",
        "sitejspreview": "'''Arrecuerdete ca tu ste vide sulamende in andeprime 'u codece JavaScript tune.'''\n'''Non g'à state angore reggistrate ninde!'''",
-       "userinvalidcssjstitle": "'''Attenziò:''' Non ge stè 'nu skin \"$1\".\nArrecuerdete ca jndr'à le file personalizzete .css e .js s'ause scrivere le titele cu le lettere piccenne, pe esembie {{ns:user}}:Foo/vector.css è diverse da {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Attenziò:''' Non ge stè 'nu skin \"$1\".\nArrecuerdete ca jndr'à le file personalizzete .css e .js s'ause scrivere le titele cu le lettere piccenne, pe esembie {{ns:user}}:Foo/vector.css è diverse da {{ns:user}}:Foo/Vector.css.",
        "updated": "(Cangiete)",
        "note": "'''Vide Bbuene:'''",
        "previewnote": "'''Arrecuerdete queste è sole 'n'andeprime.'''\nle cangiaminde non g'onne state angore reggistrate!",
        "prefs-files": "Fails",
        "prefs-custom-css": "CSS Personalizzete",
        "prefs-custom-js": "JS Personalizzete",
-       "prefs-common-css-js": "CSS/JS condivise pe tutte le sfonde:",
+       "prefs-common-config": "CSS/JS condivise pe tutte le sfonde:",
        "prefs-reset-intro": "Tu puè ausà sta pàgene pe azzerà le preferenze tue a quidde de default d'u site.\nQuiste non ge pò essere annullate.",
        "prefs-emailconfirm-label": "Conferme de l'e-mail:",
        "youremail": "Poste:",
index e4f7dd8..f7a6e44 100644 (file)
        "userjspreview": "'''Помните, что это только предварительный просмотр вашего javascript-файла, он ещё не сохранён!'''",
        "sitecsspreview": "'''Помните, что вы только предварительно просматриваете этот CSS.'''\n'''Он ещё не сохранён!'''",
        "sitejspreview": "'''Помните, что вы только предварительно просматриваете этот JavaScript-код.'''\n'''Он ещё не сохранён!'''",
-       "userinvalidcssjstitle": "'''Внимание:''' тема оформления «$1» не найдена. Помните, что пользовательские страницы .css и .js должны иметь название, состоящее только из строчных букв, например «{{ns:user}}:Некто/vector.css», а не «{{ns:user}}:Некто/Vector.css».",
+       "userinvalidconfigtitle": "'''Внимание:''' тема оформления «$1» не найдена. Помните, что пользовательские страницы .css и .js должны иметь название, состоящее только из строчных букв, например «{{ns:user}}:Некто/vector.css», а не «{{ns:user}}:Некто/Vector.css».",
        "updated": "(Обновлена)",
        "note": "'''Примечание:'''",
        "previewnote": "'''Помните, что это только предварительный просмотр.'''\nВаши изменения ещё не были сохранены!",
        "prefs-files": "Файлы",
        "prefs-custom-css": "Собственный CSS",
        "prefs-custom-js": "Собственный JS",
-       "prefs-common-css-js": "Общие CSS/JS для всех тем оформления:",
+       "prefs-common-config": "Общие CSS/JS для всех тем оформления:",
        "prefs-reset-intro": "Эта страница может быть использована для сброса ваших настроек на стандартные.\nУчтите, что это действие невозможно отменить.",
        "prefs-emailconfirm-label": "Подтверждение электронной почты:",
        "youremail": "Электронная почта:",
        "thumbnail_dest_directory": "Невозможно создать целевую директорию",
        "thumbnail_image-type": "Данный тип изображения не поддерживается",
        "thumbnail_gd-library": "Неполная конфигурация библиотеки GD, отсутствует функция $1",
+       "thumbnail_image-size-zero": "Размер файла изображения, кажется, равен нулю.",
        "thumbnail_image-missing": "По-видимому, отсутствует файл $1",
        "thumbnail_image-failure-limit": "Было сделано слишком много неудачных попыток ($1 или больше) формирования этого эскиза. Пожалуйста, повторите попытку позже.",
        "import": "Импортирование страниц",
index 2eb7f44..54bf4fc 100644 (file)
        "userjspreview": "'''Памятайте, же тестуєте а перезерате лем нагляд вашого хосновательского JavaScript-у, іщі не быв уложеный!'''",
        "sitecsspreview": "'''Памятайте, же собі перезерате лем нагляд того CSS.'''\n'''Іщі не было уложене!'''",
        "sitejspreview": "'''Памятайте, же собі перезерате лем нагляд того JavaScript-у.'''\n'''Іщі не быв уложеный!'''",
-       "userinvalidcssjstitle": "'''Увага:''' Тема взгляду „$1“ не екзістує. Не забудьте, же хосновательске .css і .js файлы хоснують малы писмена, наприклад {{ns:user}}:{{BASEPAGENAME}}/vector.css, а не {{ns:user}}:{{BASEPAGENAME}}/Vector.css.",
+       "userinvalidconfigtitle": "'''Увага:''' Тема взгляду „$1“ не екзістує. Не забудьте, же хосновательске .css і .js файлы хоснують малы писмена, наприклад {{ns:user}}:{{BASEPAGENAME}}/vector.css, а не {{ns:user}}:{{BASEPAGENAME}}/Vector.css.",
        "updated": "(Зміна уложена)",
        "note": "'''Позначка:'''&nbsp;",
        "previewnote": "'''Памятайте, же то лем нагляд.'''\nЗміны іщі не суть уложены!",
        "prefs-files": "Файлы",
        "prefs-custom-css": "Властный CSS",
        "prefs-custom-js": "Властный JS",
-       "prefs-common-css-js": "Сдїляне CSS/JS про вшыткы штілы:",
+       "prefs-common-config": "Сдїляне CSS/JS про вшыткы штілы:",
        "prefs-reset-intro": "Помочов той сторінкы можете вшыткы наставлїня вернути на імпліцітны годноты.\nТоту операцію не годен вернути назад.",
        "prefs-emailconfirm-label": "Потверджіня електронічной пошты:",
        "youremail": "Адреса електронічной пошты:",
index 0c2f265..c2805b2 100644 (file)
        "userjspreview": "<strong>एतत् केवलं सदस्यस्य JabaScript इत्यस्य प्राग्दृश्यं वर्तते इति स्मरतु । भवता/भवत्या कृतानि परिवर्तनानि एतवता न रक्षितानि !</strong>",
        "sitecsspreview": "<strong>एतत् केवलं CSS इत्यस्य प्राग्दृश्यं वर्तते इति स्मरतु । भवता/भवत्या कृतानि परिवर्तनानि एतवता न रक्षितानि !</strong>",
        "sitejspreview": "<strong>एतत् केवलं JavaScript इत्यस्य प्राग्दृश्यं वर्तते इति स्मरतु । भवता/भवत्या कृतानि परिवर्तनानि एतवता न रक्षितानि !</strong>",
-       "userinvalidcssjstitle": "'''पूर्वसूचना:'''  \"$1\" इति त्वक् न विद्यते।\nयोजकपरिवर्तिते .css, .js सञ्चिके लघु-आङ्ग्लवर्णमालायाः वर्णैः लिख्येते । उदा. {{ns:user}}:Foo/Vector.css एवं न लेखनीयम् । लघुवर्णैः {{ns:user}}:Foo/vector.css एवं लेखनीयम् ।",
+       "userinvalidconfigtitle": "'''पूर्वसूचना:'''  \"$1\" इति त्वक् न विद्यते।\nयोजकपरिवर्तिते .css, .js सञ्चिके लघु-आङ्ग्लवर्णमालायाः वर्णैः लिख्येते । उदा. {{ns:user}}:Foo/Vector.css एवं न लेखनीयम् । लघुवर्णैः {{ns:user}}:Foo/vector.css एवं लेखनीयम् ।",
        "updated": "(नवीकृतम् (updated))",
        "note": "'''सूचना:'''",
        "previewnote": "<strong>एतत् केवलं प्राग्दृश्यं वर्तते इति स्मरतु । भवता/भवत्या कृतानि परिवर्तनानि एतवता न रक्षितानि !</strong>",
        "prefs-files": "सञ्चिकाः",
        "prefs-custom-css": "स्वानुकूलसम्पादितं CSS",
        "prefs-custom-js": "स्वानुकूलसम्पादितं JavaScript",
-       "prefs-common-css-js": "सर्वासां त्वचां (of skins) कृते CSS/JavaScript:",
+       "prefs-common-config": "सर्वासां त्वचां (of skins) कृते CSS/JavaScript:",
        "prefs-reset-intro": "भवान्/भवती अस्य पृष्ठस्य साहाय्येन स्वस्य इष्टतमविकल्पान् मूलविकि-विकल्पसदृशं स्थापयितुं (कर्तुं) शक्नोति ।\nपरन्तु ततः भवान्/भवती पूर्ववत् स्थितिं प्राप्तुं न शक्ष्यति ।",
        "prefs-emailconfirm-label": "वि-पत्रं दृढीक्रियताम् :",
        "youremail": "वि-पत्रसङ्केतः :",
index 5820e6e..2af5131 100644 (file)
        "userjspreview": "'''Умнума: бу JavaScript тургутуутэ эрэ, уларыппытыҥ бигэргэтиллэ илик!'''",
        "sitecsspreview": "'''Бу CSS бигэргэппэккэ көрө олороргун умнума.'''\n'''Бигэргэтиллэ илик!'''",
        "sitejspreview": "'''Бу JavaScript-куодун бигэргэппэккэ көрө олороргун умнума.'''\n'''Бигэргэтиллэ илик!'''",
-       "userinvalidcssjstitle": "'''Болҕой:''' Бу тиэмэ «$1» суох. Кыттааччы .css и .js сирэйдэрэ кыра буукубаннан суруллуохтаахтар, холобур «{{ns:user}}:Ньургун/vector.css», маннык буолуо суохтаах «{{ns:user}}:Ньургун/Vector.css».",
+       "userinvalidconfigtitle": "'''Болҕой:''' Бу тиэмэ «$1» суох. Кыттааччы .css и .js сирэйдэрэ кыра буукубаннан суруллуохтаахтар, холобур «{{ns:user}}:Ньургун/vector.css», маннык буолуо суохтаах «{{ns:user}}:Ньургун/Vector.css».",
        "updated": "(Саҥардылынна)",
        "note": "'''Хос быһаарыы:'''",
        "previewnote": "'''Бу барыллаан көрүү эрэ.'''\nАтын уларытыы бигэргэтиллэ илик!",
        "prefs-files": "Билэлэр",
        "prefs-custom-css": "Бэйэ CSS",
        "prefs-custom-js": "Бэйэ JS",
-       "prefs-common-css-js": "Бары тиэмэлэргэ биир CSS/JS",
+       "prefs-common-config": "Бары тиэмэлэргэ биир CSS/JS",
        "prefs-reset-intro": "Бу сирэй көмөтүнэн туруорууларгын саҥаттан туруорар турукка төннөрүөххүн сөп.\nМаны бигэргэттэххинэ билигин баар туруоруулары дэбигис сөргүппэккин.",
        "prefs-emailconfirm-label": "Эл. почтаны бигэргэтии:",
        "youremail": "E-mail-ыҥ:",
index 22178a0..7afbb81 100644 (file)
                        "Ramjit Tudu"
                ]
        },
-       "tog-underline": "Joṛaoko latarre dag udugoḱma:",
-       "tog-hideminor": "Nitaḱ bodolaḱre huḍiṅ kạmi danaṅme",
+       "tog-underline": "ᱡᱚᱱᱚᱲ ᱞᱟᱛᱟᱨᱨᱮ ᱫᱟᱜᱽ ᱩᱫᱩᱜᱽᱢᱮ:",
+       "tog-hideminor": "ᱱᱤᱛᱚᱜ ᱵᱚᱫᱚᱞᱟᱜᱨᱮ ᱦᱩᱰᱤᱧ ᱥᱟᱯᱲᱟᱣ ᱩᱠᱩᱭᱢᱮ",
        "tog-hidepatrolled": "ᱡᱚᱲᱟᱣᱠᱟᱱ ᱥᱟᱯᱲᱟᱣᱠᱚ ᱱᱟᱣᱟ ᱵᱚᱫᱚᱞ ᱠᱷᱚᱱ ᱩᱠᱩᱭᱢᱮ",
        "tog-newpageshidepatrolled": "ᱱᱟᱣᱟ ᱥᱟᱦᱴᱟ ᱛᱟᱹᱞᱠᱟᱹ ᱠᱷᱚᱱ ᱵᱤᱰᱟᱹᱣᱠᱟᱱ ᱥᱟᱦᱴᱟᱠᱚ ᱩᱠᱩᱭᱢᱮ",
        "tog-hidecategorization": "ᱥᱟᱦᱴᱟ ᱨᱮᱱᱟᱜ ᱛᱷᱚᱠ ᱠᱚ ᱫᱟᱱᱟᱝ",
-       "tog-extendwatchlist": "Khạli nitoḱ bodolko do baṅ, joto bodolkodo ńeloḱ tạlikare phaylaomẽ.",
+       "tog-extendwatchlist": "ᱠᱷᱟᱹᱞᱤ ᱱᱮᱛᱚᱜ ᱵᱚᱫᱚᱞᱟᱜ ᱫᱚ ᱵᱟᱝ, ᱡᱚᱛᱚ ᱵᱟᱫᱚᱞᱟᱜ ᱜᱮ ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹᱨᱮ ᱯᱷᱟᱭᱞᱟᱣᱢᱮ",
        "tog-usenewrc": "Nahaḱ bodolakanaḱko ar nojor reaḱ pahaṭare bodolaḱko mit́são ńelńam",
-       "tog-numberheadings": "Mukhiạ kathako do actege piṛhipiṛhite sajaḱma",
+       "tog-numberheadings": "ᱢᱩᱬᱩᱛ ᱠᱟᱛᱷᱟᱠᱩ ᱫᱚ ᱟᱪᱛᱮᱜᱮ ᱯᱤᱲᱦᱤ ᱯᱤᱲᱦᱤᱛᱮ ᱥᱟᱡᱟᱜᱢᱟ",
        "tog-showtoolbar": "ᱦᱟᱹᱛᱤᱭᱟᱹᱨ ᱠᱟᱸᱛ ᱩᱫᱩᱜᱽ ᱢᱮ",
        "tog-editondblclick": "Bar dhao lin kate sakam torjomão reaḱ ạidari emogoḱma",
        "tog-editsectiononrightclick": "Pahaṭa reaḱ pahaṭa guṭkathare jojom seć lin hotete <br /> pahaṭa sompadon lạgitte ektiạr em hoyoḱma (JavaScript)",
-       "tog-watchcreations": "Ińaḱ tear sakam ar rakaṕ páelko ińaḱ ńelogoḱ tạlikare ńeloḱ ma",
-       "tog-watchdefault": "Ińaḱ purạoakanaḱ sakam ar phayelko do ińaḱ ńeloḱ tạlikare joṛaoḱma",
-       "tog-watchmoves": "Ińaḱ ocoḱ sakam ar phayelko inyaḱ nojor sakamre joṛaḱma",
-       "tog-watchdeletion": "Sakamko tońgeyme Ińaḱ ńeloḱ tạlika khon get́ giḍikam",
-       "tog-minordefault": "Etohoṕre sanam joṛao purạoanaḱko do bekor unuduḱ lekate cinhạkma",
-       "tog-previewontop": "Joṛao bakso purạo lahare unuduḱ hoyoḱma",
-       "tog-previewonfirst": "Pạhil joṛao purạore unuduḱ hoyoḱma",
+       "tog-watchcreations": "ᱥᱟᱦᱴᱟᱠᱩ ᱥᱮᱞᱮᱫᱢᱮ ᱤᱧᱟᱜ ᱛᱮᱭᱟᱨ ᱟᱨ ᱨᱮᱫᱠᱩ ᱤᱧᱟᱜ ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹᱨᱮ ᱨᱟᱠᱟᱵ ᱢᱮ",
+       "tog-watchdefault": "ᱤᱧᱟᱜ ᱥᱟᱯᱲᱟᱣ ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹᱨᱮ ᱥᱟᱦᱴᱟᱠᱩ ᱟᱨ ᱨᱮᱫᱠᱩ ᱥᱮᱞᱮᱫ ᱢᱮ",
+       "tog-watchmoves": "ᱤᱧᱟᱜ ᱥᱟᱯᱲᱟᱣ ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹᱨᱮ ᱥᱟᱦᱴᱟᱠᱩ ᱟᱨ ᱨᱮᱫᱠᱩ ᱚᱪᱟᱜᱽ ᱢᱮ",
+       "tog-watchdeletion": "ᱤᱧᱟᱜ ᱥᱟᱯᱲᱟᱣ ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹᱨᱮ ᱥᱟᱦᱴᱟᱠᱩ ᱟᱨ ᱨᱮᱫᱠᱩ ᱜᱮᱫ ᱜᱤᱰᱤᱭ ᱢᱮ",
+       "tog-minordefault": "ᱮᱛᱦᱚᱵᱨᱮ ᱥᱟᱱᱟᱢ ᱥᱟᱯᱲᱟᱣᱠᱩ ᱦᱩᱰᱤᱧ ᱞᱮᱠᱟᱛᱮ ᱪᱤᱱᱦᱟᱹ ᱠᱟᱜ ᱢᱮ",
+       "tog-previewontop": "ᱥᱟᱯᱲᱟᱣ ᱵᱟᱠᱥᱳ ᱞᱟᱦᱟᱨᱮ ᱩᱱᱩᱫᱩᱜ ᱩᱫᱩᱜᱽ ᱢᱮ",
+       "tog-previewonfirst": "ᱯᱟᱹᱦᱤᱞ ᱥᱟᱯᱲᱟᱣ ᱨᱮ ᱩᱱᱩᱫᱩᱜ ᱩᱫᱩᱜᱽ ᱢᱮ",
        "tog-enotifwatchlistpages": "E-mailạńme one tinre in̕aḱ n̕eloḱ tạlika do bodolok",
-       "tog-enotifusertalkpages": "E-mailạn̕me one tinre in̕aḱ roṛaḱ laṛcaṛ sakam do bodoloḱa",
+       "tog-enotifusertalkpages": "ᱤ-ᱢᱮᱞ ᱟᱹᱧᱢᱮ ᱛᱤᱱᱨᱮ ᱤᱧᱟᱜ ᱨᱚᱲ ᱥᱟᱦᱴᱟ ᱵᱚᱫᱚᱞᱜ-ᱟ",
        "tog-enotifminoredits": "E-mailạn̕me arhõ one tinre in̕aḱ sakamre huḍiń kạmi hoyoḱ",
-       "tog-enotifrevealaddr": "Dhạrwạk reaḱ sakamre ińaḱ e-mail ṭhikạna sodor hoyoḱma",
-       "tog-shownumberswatching": "Ńelok laṛcaṛkoaḱ songkha uduḱme",
+       "tog-enotifrevealaddr": "ᱰᱷᱟᱹᱨᱣᱟᱜ ᱥᱟᱦᱴᱟᱨᱮ ᱤᱧᱟᱜ e-mail ᱴᱷᱤᱠᱱᱟ ᱥᱚᱫᱚᱨ ᱦᱩᱭᱩᱜ ᱢᱟ",
+       "tog-shownumberswatching": "ᱧᱮᱞᱚᱜ ᱵᱮᱵᱟᱦᱟᱨᱤᱡ ᱠᱯᱣᱟᱜ ᱮᱞᱮᱞ ᱩᱫᱩᱜᱽ ᱢᱮ",
        "tog-oldsig": "ᱟᱢᱟᱜ ᱥᱩᱦᱤ:",
-       "tog-fancysig": "Signạcar do wikiṭesk hisạbte moneyemẽ (jahan acte hoyoḱ joṛao bạgikate)",
+       "tog-fancysig": "ᱥᱩᱦᱤ ᱫᱚ ᱣᱤᱠᱤ ᱚᱞ ᱦᱤᱥᱟᱹᱵᱛᱮ ᱢᱚᱱᱮᱭᱢᱮ (ᱟᱪᱛᱮ ᱦᱩᱭᱠᱟᱱ ᱡᱚᱱᱚᱲ ᱵᱟᱹᱜᱤᱠᱟᱛᱮ)",
        "tog-uselivepreview": "Jewet́ ńeloḱ beoharme (JavaScript jaruṛ menaḱa)",
-       "tog-forceeditsummary": "Khạli sompadon guṭkatha em oktere iń baḍae ocoyiń hoyoḱma",
-       "tog-watchlisthideown": "Ńeloḱ talikare ińaḱ joṛao kamiko danaṅme",
-       "tog-watchlisthidebots": "Boṭreaḱ sompadon kạmiko do ńeloḱ tạlika khon danaṅmẽ",
-       "tog-watchlisthideminor": "Ńeloḱ tạlikare ińak huḍiṅ joṛao kạmiko danaṅme",
-       "tog-watchlisthideliu": "Ńeloḱ tạlikareaḱ ekaunṭ bolok beoharkoaḱ sompadon danaṅ hoyoḱma",
-       "tog-watchlisthideanons": "Ńeloḱ tạlikare baṅ ńutamanić beoharićaḱ sompadonko danaṅ hoyoḱma",
-       "tog-watchlisthidepatrolled": "Biḍạen sompadonko do ńeloḱ sakamre danaṅmẽ",
+       "tog-forceeditsummary": "ᱠᱷᱟᱹᱞᱤ ᱥᱟᱯᱲᱟᱣ ᱜᱤᱴ ᱠᱟᱛᱷᱟ ᱮᱢ ᱚᱠᱛᱚᱨᱮ ᱤᱧ ᱵᱟᱰᱟᱭ ᱚᱪᱚᱭᱤᱧ ᱦᱩᱭᱩᱜᱢᱟ",
+       "tog-watchlisthideown": "ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹ ᱠᱷᱚᱱ ᱤᱧᱟᱜ ᱥᱟᱯᱲᱟᱣᱠᱩ ᱩᱠᱩᱭᱢᱮ",
+       "tog-watchlisthidebots": "ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹ ᱠᱷᱚᱱ ᱵᱚᱴ ᱥᱟᱯᱲᱟᱣᱠᱩ ᱩᱠᱩᱭᱢᱮ",
+       "tog-watchlisthideminor": "ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹ ᱠᱷᱚᱱ ᱦᱩᱰᱤᱧ ᱥᱟᱯᱲᱟᱣᱠᱩ ᱩᱠᱩᱭᱢᱮ",
+       "tog-watchlisthideliu": "ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹ ᱠᱷᱚᱱ ᱵᱚᱞᱚ ᱟᱠᱟᱱ ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱥᱟᱯᱲᱟᱣᱠᱩ ᱩᱠᱩᱭᱢᱮ",
+       "tog-watchlisthideanons": "ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹ ᱠᱷᱚᱱ ᱵᱟᱭ ᱵᱚᱞᱚ ᱟᱠᱟᱱ ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱥᱟᱯᱲᱟᱣᱠᱩ ᱩᱠᱩᱭᱢᱮ",
+       "tog-watchlisthidepatrolled": "ᱧᱮᱞᱚᱜ ᱛᱟᱹᱞᱠᱟᱹ ᱠᱷᱚᱱ ᱵᱤᱰᱟᱹᱣᱮᱱ ᱥᱟᱯᱲᱟᱣᱠᱩ ᱩᱠᱩᱭᱢᱮ",
        "tog-watchlisthidecategorization": "ᱥᱟᱦᱴᱟ ᱨᱮᱱᱟᱜ ᱛᱷᱚᱠ ᱠᱚ ᱫᱟᱱᱟᱝ",
-       "tog-ccmeonemails": "E-mail reaḱ kopy kulạńme Eṭaḱ laṛcaṛko kulakome",
-       "tog-diffonly": "Farak reaḱ latar sakamre babotko baṅ udugoḱma",
-       "tog-showhiddencats": "Danaṅ rokom sokomko uduḱmẽ",
+       "tog-ccmeonemails": "E-mail ᱠᱩ ᱨᱮᱭᱟᱜ ᱠᱚᱯᱤᱠᱩ ᱠᱩᱞᱢᱮ ᱡᱟᱸᱦᱟᱸ ᱤᱧ ᱮᱴᱟᱜ ᱵᱮᱵᱦᱟᱨᱤᱡᱠᱩ ᱴᱷᱮᱱᱤᱧ ᱠᱩᱞ ᱞᱮ",
+       "tog-diffonly": "ᱯᱷᱟᱨᱟᱠ ᱨᱮᱭᱟᱜ ᱞᱟᱛᱟᱨ ᱥᱟᱦᱴᱟᱨᱮ ᱵᱟᱵᱚᱛᱠᱩ ᱵᱟᱝ ᱩᱫᱩᱜᱚᱜ ᱢᱟ",
+       "tog-showhiddencats": "ᱩᱠᱩ ᱛᱷᱚᱠᱠᱩ ᱩᱫᱩᱜᱽᱢᱮ",
        "tog-norollbackdiff": "rollback tayomte farak alom uduga",
-       "underline-always": "Sanam okte",
-       "underline-never": "Tis hõ ban̕",
+       "underline-always": "ᱥᱟᱨᱟ ᱜᱷᱟᱹᱲᱤᱡ",
+       "underline-never": "ᱛᱤᱥ ᱦᱚᱸ ᱵᱟᱝ",
        "underline-default": "Browjarre cetlekate em hoy akana",
-       "editfont-style": "Sompadon ṭhại reaḱ fonṭ sṭayel:",
-       "editfont-monospace": "Monoespeć fonṭ",
-       "editfont-sansserif": "Sans-serif fon",
-       "editfont-serif": "Serif fon",
+       "editfont-style": "ᱥᱟᱯᱲᱟᱣ ᱡᱟᱜᱟ ᱨᱮᱱᱟᱜ ᱯᱷᱚᱱᱴ ᱮᱥᱴᱟᱭᱤᱞ:",
+       "editfont-monospace": "Monospaced font",
+       "editfont-sansserif": "Sans-serif font",
+       "editfont-serif": "Serif font",
        "sunday": "ᱥᱤᱸᱜᱮ ᱢᱟᱦᱟᱸ",
        "monday": "ᱚᱛᱮ ᱢᱟᱦᱟᱸ",
        "tuesday": "ᱵᱟᱞᱮ ᱢᱟᱦᱟᱸ",
        "category-media-header": "\"$1\" ᱵᱟᱵᱚᱛ ᱨᱮᱭᱟᱜ ᱢᱤᱰᱤᱭᱟ ᱛᱷᱚᱠ",
        "category-empty": "<em>ᱱᱚᱣᱟ ᱛᱷᱚᱠ ᱨᱮ ᱱᱮᱛᱚᱜ ᱩᱱᱩᱫᱩᱜ ᱥᱟᱦᱴᱟᱠᱚ ᱥᱮ ᱢᱤᱰᱤᱭᱟ ᱵᱟᱱᱩᱜ-ᱟ᱾</em>",
        "hidden-categories": "{{PLURAL:$1|ᱫᱟᱱᱟᱝ ᱛᱷᱚᱠ|ᱫᱟᱱᱟᱝ ᱛᱷᱚᱠᱠᱩ}}",
-       "hidden-category-category": "Uku akan rokom sokom ko",
+       "hidden-category-category": "ᱩᱠᱩ ᱛᱷᱚᱠ ᱠᱩ",
        "category-subcat-count": "{{PLURAL:$2| ᱱᱚᱣᱟ ᱛᱷᱚᱠ ᱨᱮ ᱱᱚᱣᱟᱠᱩ ᱦᱩᱰᱤᱧ ᱛᱷᱚᱠ ᱠᱩ ᱢᱮᱱᱟᱜ-ᱟ|ᱱᱚᱣᱟ ᱛᱷᱚᱠ ᱨᱮ ᱢᱮᱱᱟᱜ-ᱟ {{PLURAL:$1|ᱦᱩᱰᱤᱧ ᱛᱷᱚᱠᱠᱩ}}, ᱡᱚᱛᱚᱠᱚᱛᱮ $2}}",
-       "category-subcat-count-limited": "Noa rokom sokomre latar reaḱ {{PLURAL:$1 gan kạṭic rokom sokom $1gan kạtic rokom sokom menaḱa}}",
+       "category-subcat-count-limited": "ᱱᱚᱣᱟ ᱛᱷᱚᱠ ᱨᱮᱫᱚ ᱱᱚᱣᱟᱠᱩ {{PLURAL:$1 ᱜᱟᱱ ᱦᱩᱰᱤᱧ ᱛᱷᱚᱠ $1 ᱜᱟᱱ ᱦᱩᱰᱤᱧ ᱛᱷᱚᱠᱠᱩ ᱢᱮᱱᱟᱜ-ᱟ}}",
        "category-article-count": "{{PLURAL:$2| ᱱᱚᱣᱟ ᱛᱷᱚᱠ ᱨᱮᱫᱚ ᱮᱠᱮᱱ ᱛᱟᱭᱚᱢᱛᱮᱱᱟᱜ ᱥᱟᱦᱴᱟ ᱢᱮᱱᱟᱜ-ᱟ᱾| ᱛᱟᱭᱚᱢ {{PLURAL:$2| ᱥᱟᱦᱴᱟ ᱫᱚ |$1 ᱥᱟᱦᱴᱟᱠᱚ ᱠᱟᱱᱟ}} ᱱᱤᱭᱟᱹ ᱛᱷᱚᱠᱨᱮ, ᱥᱟᱱᱟᱢᱠᱚᱛᱮ ᱦᱩᱭᱩᱜ ᱠᱟᱱᱟ $2 ᱾}}",
-       "category-article-count-limited": "Noa {{PLURAL:$1 sakam sakamko}} rokom sokomre menaḱa.",
+       "category-article-count-limited": "ᱱᱚᱣᱟ {{PLURAL:$1 ᱥᱟᱦᱴᱟ ᱥᱟᱦᱴᱟᱠᱩ}} ᱛᱷᱚᱠᱨᱮ ᱢᱮᱱᱟᱜ-ᱟ᱾",
        "category-file-count": "{{PLURAL:$2|ᱱᱚᱣᱟ ᱛᱷᱚᱠ ᱨᱮᱭᱟᱜ ᱩᱱᱩᱫᱩᱜ ᱫᱚ ᱮᱠᱮᱱ ᱯᱟᱸᱡᱟᱸ ᱨᱮᱫ ᱜᱮ᱾| ᱱᱚᱣᱟ ᱯᱟᱸᱡᱟᱸ \n{{PLURAL:$1|ᱨᱮᱫ ᱫᱚ|$1 ᱨᱮᱫ ᱫᱚᱠᱚ}} ᱱᱤᱭᱟᱹ ᱛᱷᱚᱠᱨᱮ $2 ᱡᱚᱛᱚᱜᱮ᱾}}",
-       "category-file-count-limited": "Latar reaḱ {{PLURAL:$1 rẽt rẽtko}} noa rokom sokomre menaḱa.",
+       "category-file-count-limited": "ᱱᱚᱣᱟ {{PLURAL:$1 ᱨᱮᱫ ᱨᱮᱫᱠᱩ }} ᱛᱷᱚᱠᱨᱮ ᱢᱮᱱᱟᱜ-ᱟ᱾",
        "listingcontinuesabbrev": "ᱪᱟᱞᱟᱜ ᱠᱟᱱᱟ",
        "index-category": "ᱩᱱᱩᱫᱩᱜ-ᱟᱱ ᱥᱟᱦᱴᱟᱠᱚ",
        "noindex-category": "ᱩᱱᱩᱫᱩᱜ ᱵᱟᱹᱱᱩᱜ-ᱟᱱ ᱥᱟᱦᱴᱟᱠᱚ",
        "article": "ᱩᱱᱩᱫᱩᱜ ᱥᱟᱦᱴᱟ",
        "newwindow": "(ᱱᱟᱣᱟ ᱡᱟᱱᱞᱟᱨᱮ ᱡᱷᱤᱡ ᱢᱮ)",
        "cancel": "ᱵᱟᱫᱽ",
-       "moredotdotdot": "Aema",
+       "moredotdotdot": "ᱵᱟᱹᱲᱛᱤ...",
        "morenotlisted": "ᱱᱚᱣᱟ ᱛᱟᱹᱞᱠᱟᱹ ᱫᱚ ᱯᱟᱥᱮᱡ ᱟᱫᱷᱟ ᱜᱮᱭᱟ᱾",
        "mypage": "ᱥᱟᱦᱴᱟ",
        "mytalk": "ᱨᱚᱲ",
        "anontalk": "ᱨᱚᱲ",
        "navigation": "ᱟᱹᱪᱩᱨᱵᱟᱲᱟ",
        "and": "&#32;ᱟᱨ",
-       "faq": "Baḍae kupuliko",
-       "actions": "Kạmi",
+       "faq": "FAQ",
+       "actions": "ᱠᱟᱹᱢᱤᱠᱩ",
        "namespaces": "ᱧᱤᱛᱩᱢ ᱡᱟᱜᱟ",
        "variants": "ᱮᱴᱟᱜᱠᱳ",
        "navigation-heading": "ᱟᱪᱩᱨᱵᱟᱲᱟ ᱢᱮᱱᱩ",
-       "errorpagetitle": "vul",
+       "errorpagetitle": "ᱦᱩᱲᱟᱹᱜ",
        "returnto": "$1 ᱛᱮ ᱨᱩᱭᱟᱹᱲᱚᱜ ᱢᱮ",
        "tagline": " {{SITENAME}} ᱠᱷᱚᱱ",
        "help": "ᱜᱚᱸᱲᱚᱸ",
        "search": "ᱥᱮᱸᱫᱽᱨᱟ",
        "searchbutton": "ᱥᱮᱸᱫᱽᱨᱟ",
-       "go": "Calaḱme",
+       "go": "ᱪᱟᱞᱟᱜ ᱢᱮ",
        "searcharticle": "ᱪᱟᱞᱟᱜ ᱢᱮ",
        "history": "ᱥᱟᱦᱴᱟ ᱱᱟᱜᱟᱢ",
        "history_short": "ᱱᱟᱜᱟᱢ",
        "history_small": "ᱱᱟᱜᱟᱢ",
-       "updatedmarker": "Ińaḱ mucạt hiripor khon nitaḱ halot",
+       "updatedmarker": "ᱤᱧᱟᱜ ᱢᱩᱪᱟᱹᱛ ᱦᱤᱨᱤᱯᱚᱨ ᱠᱷᱚᱱ ᱱᱤᱛᱚᱜ ᱦᱟᱞᱚᱛ",
        "printableversion": "ᱪᱷᱟᱯᱟ ᱜᱟᱱᱚᱜ ᱚᱰᱚᱫ",
        "permalink": "ᱛᱤᱨᱮᱡᱩᱜᱮ ᱡᱚᱱᱚᱲ",
-       "print": "Chapa",
+       "print": "ᱪᱷᱟᱯᱟ",
        "view": "ᱩᱰᱩᱜᱽᱢᱮ",
        "view-foreign": "$1 ᱨᱮ ᱧᱮᱞ ᱢᱮ",
        "edit": "ᱥᱟᱯᱲᱟᱣ",
        "create": "ᱛᱮᱭᱟᱨ",
        "create-local": "ᱢᱮᱥᱟᱭᱢᱮ ᱠᱟᱛᱷᱟ ᱠᱚ",
        "delete": "ᱜᱮᱫ ᱜᱤᱰᱤ",
-       "undelete_short": "Baṅ getgiḍilena {{PLURAL:$1 1ṭen joṛao $ joṛaoko}}",
-       "viewdeleted_short": "{{PLURAL:$1 gan ocoḱ sompadok $1 gan ocoḱ sompadon}} udugmẽ",
-       "protect": "banchao'",
-       "protect_change": "Judạ",
-       "unprotect": "Bodol ban̕cao",
+       "undelete_short": "ᱵᱟᱝ ᱜᱮᱫ ᱜᱤᱰᱤᱞᱮᱱᱟ {{PLURAL:$1 ᱢᱤᱫᱴᱮᱱ ᱡᱚᱱᱚᱲ $1 ᱡᱚᱱᱚᱲᱠᱩ}}",
+       "viewdeleted_short": "{{PLURAL:$1 ᱜᱟᱱ ᱜᱮᱫ ᱜᱤᱰᱤ ᱟᱠᱟᱱ ᱥᱟᱯᱲᱟᱣᱠᱩ $1 ᱜᱟᱱ ᱜᱮᱫ ᱜᱤᱰᱤ ᱟᱠᱟᱱ ᱥᱟᱯᱲᱟᱣᱠᱩ}} ᱩᱫᱩᱜᱽᱢᱮ",
+       "protect": "ᱨᱩᱠᱷᱤᱭᱟᱹ",
+       "protect_change": "ᱵᱚᱫᱚᱞ",
+       "unprotect": "ᱵᱚᱫᱚᱞ ᱨᱩᱠᱷᱤᱭᱟᱹ",
        "newpage": "ᱱᱟᱶᱟ ᱥᱟᱦᱴᱟ",
        "talkpagelinktext": "ᱨᱚᱲ",
        "specialpage": "ᱵᱤᱥᱮᱥ ᱥᱟᱦᱴᱟ",
        "mediawikipage": "ᱠᱷᱚᱵᱚᱨ ᱥᱟᱦᱴᱟ ᱧᱮᱞᱢᱮ",
        "templatepage": "ᱪᱷᱟᱸᱪ ᱥᱟᱦᱴᱟ ᱩᱫᱩᱜᱽ ᱢᱮ",
        "viewhelppage": "ᱜᱚᱸᱲᱚ ᱥᱟᱦᱴᱟ ᱧᱮᱞᱢᱮ",
-       "categorypage": "Babot reaḱ rokom sokom udugmẽ",
-       "viewtalkpage": "Galmarao ńelme",
+       "categorypage": "ᱛᱷᱚᱠ ᱥᱟᱦᱴᱟ ᱧᱮᱞᱢᱮ",
+       "viewtalkpage": "ᱜᱟᱞᱢᱟᱨᱟᱣ ᱧᱮᱞᱢᱮ",
        "otherlanguages": "ᱮᱴᱟᱜ ᱯᱟᱹᱨᱥᱤ ᱛᱮ",
        "redirectedfrom": "($1 ᱠᱷᱚᱱ ᱟᱹᱪᱩᱨ ᱦᱮᱡᱠᱟᱱᱟ)",
        "redirectpagesub": "ᱵᱟᱝ ᱥᱚᱡᱽᱦᱮ ᱥᱟᱦᱴᱟ",
        "redirectto": "ᱪᱟᱞᱟᱜ ᱠᱟᱱᱟ:",
        "lastmodifiedat": "ᱱᱚᱶᱟ ᱥᱟᱦᱴᱟ ᱢᱩᱪᱟᱹᱫ ᱫᱷᱟᱣ ᱵᱚᱫᱚᱞ ᱟᱠᱟᱱᱟ  $1 ᱢᱟᱹᱦᱤᱛ,  $2 ᱚᱠᱛᱚᱨᱮ",
-       "viewcount": "Noa sakamdo {{PLURAL:$1 dhom $1 dhom}} udug hoena.",
-       "protectedpage": "Rukhíạ sakamko",
+       "viewcount": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱫᱚ {{PLURAL:$1 ᱫᱷᱟᱶ $1 ᱫᱷᱟᱶ}} ᱩᱫᱩᱜ ᱦᱩᱭᱮᱱᱟ᱾",
+       "protectedpage": "ᱨᱩᱠᱷᱤᱭᱟᱹ ᱥᱟᱦᱴᱟ",
        "jumpto": "ᱫᱚᱱᱢᱮ :",
        "jumptonavigation": "ᱟᱹᱪᱩᱨᱵᱟᱲᱟ",
        "jumptosearch": "ᱥᱮᱸᱫᱽᱨᱟ",
        "view-pool-error": "Ikạkańmẽ, sarvarre nitoḱ do aḍi cap menaḱa.\nẠḍi aema beoharko noa sakam ńel lạgit́ko kurumuṭueda.\nNãwate noa sakam ńel kurumuṭuy lạgit́te dayakate mit́ghạṛi tạṅgiymẽ.\n$1",
        "pool-timeout": "Somoy paromena cạbi lạgit́te tạṅgi hoyoḱkana",
        "pool-queuefull": "Pool queue is full",
-       "pool-errorunknown": "Bań baḍayaḱ bhul",
+       "pool-errorunknown": "ᱵᱟᱝ ᱵᱟᱰᱟᱭ ᱦᱩᱲᱟᱹᱜ",
        "aboutsite": "ᱵᱟᱵᱚᱛ {{SITENAME}}",
        "aboutpage": "Project: ᱵᱟᱵᱚᱛ",
        "copyright": "ᱩᱱᱩᱫᱩᱜ ᱫᱚ ᱧᱟᱢᱚᱜ-ᱟ $1 ᱞᱮᱠᱟᱛᱮ  ᱵᱟᱝᱠᱷᱟᱱ ᱚᱞ ᱛᱟᱦᱮᱱᱟ",
        "helppage-top-gethelp": "ᱜᱚᱸᱲᱚᱸ",
        "mainpage": "ᱢᱩᱬᱩᱛ ᱥᱟᱦᱴᱟ",
        "mainpage-description": "ᱢᱩᱬᱩᱛ ᱥᱟᱦᱴᱟ",
-       "policy-url": "Project:Ritiniti",
+       "policy-url": "Project:ᱨᱤᱛᱤᱱᱤᱛᱤ",
        "portal": "ᱜᱩᱥᱴᱤ ᱵᱚᱞᱚᱜ ᱫᱩᱭᱟᱹᱨ",
        "portal-url": "Project:ᱠᱷᱩᱴ ᱵᱚᱞᱚᱱ ᱦᱚᱨ",
        "privacy": "ᱩᱠᱩ ᱮᱠᱛᱤᱭᱟᱨ",
        "privacypage": "Project: ᱩᱠᱩ ᱮᱠᱛᱤᱭᱟᱨ",
-       "badaccess": "Ektiạr vul",
+       "badaccess": "ᱟᱹᱭᱫᱟᱹᱨᱤ ᱦᱩᱲᱟᱹᱜ",
        "badaccess-group0": "Am do oka kạmi lạgit́em aroj akat́, ona kạmi purạo lạgit́te ạidạri do bạnuḱa.",
        "badaccess-groups": "Am do oka kạmim menjoṅkan ona do khạli {{PLURAL:$2 rạsiạkore noa rạsiạreaḱ mit́ṭenre}} mitṭen beoharić sompadon daṛeyaḱa: $1.",
        "versionrequired": "ᱢᱤᱰᱤᱭᱟ ᱩᱭᱠᱤ ᱨᱮᱭᱟᱜ $1 ᱱᱟᱣᱟ ᱵᱷᱟᱨᱥᱚᱱ ᱡᱟᱹᱨᱩᱲᱟ",
        "versionrequiredtext": "Version $1 of MediaWiki is required to use this page.\nSee [[Special:Version|version page]].",
-       "ok": "Ṭhik gea",
+       "ok": "ᱴᱷᱤᱠ á±\9cᱮᱭá±\9f",
        "retrievedfrom": "\"$1\" ᱠᱷᱚᱱ ᱧᱟᱢ ᱟᱹᱜᱩᱭ",
        "youhavenewmessages": "{{PLURAL:$3|ᱟᱢᱟᱜ ᱢᱮᱱᱟᱜ-ᱟ}} $1 ($2)᱾",
        "youhavenewmessagesfromusers": "{{PLURAL:$4|ᱟᱢ ᱫᱚ}} $1 ᱠᱷᱚᱱ {{PLURAL:$3|ᱟᱨᱢᱤᱫ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ|$3 ᱵᱷᱮᱵᱷᱟᱨᱩᱭᱟᱹ}} ($2) ᱾",
        "viewsourcelink": "ᱯᱷᱮᱰᱟᱛ ᱦᱚᱨ ᱧᱮᱞᱢᱮ",
        "editsectionhint": "ᱥᱟᱯᱲᱟᱣ ᱡᱟᱭᱜᱟ: $1",
        "toc": "ᱩᱱᱩᱫᱩᱜ",
-       "showtoc": "Uduḱme",
-       "hidetoc": "uku, Danaṅ",
-       "collapsible-collapse": "Murchạo caba",
-       "collapsible-expand": "Phaylao",
+       "showtoc": "ᱥᱚᱫᱚᱨ",
+       "hidetoc": "ᱫᱟᱱᱟᱝ",
+       "collapsible-collapse": "ᱢᱩᱨᱪᱷᱟᱹᱣ ᱪᱟᱵᱟ",
+       "collapsible-expand": "ᱯᱷᱟᱭᱞᱟᱣ",
        "confirmable-yes": "ᱦᱮᱸ",
        "confirmable-no": "ᱵᱟᱝ",
        "thisisdeleted": "ᱧᱮᱞ ᱥᱮ ᱨᱩᱭᱟᱹᱲ ᱫᱚᱲᱦᱟ $1?",
-       "viewdeleted": "$1 Ńelme",
+       "viewdeleted": "$1 ᱧᱮᱞᱢᱮ",
        "restorelink": "{{PLURAL:$1 mit́ṭen ocoḱgiḍi sompadon $1 gan udug giḍi sompadon}}",
-       "feedlinks": "Jom oco",
+       "feedlinks": "ᱟᱡᱚ:",
        "feed-invalid": "Garhak feed reaḱ rokom do ạnlekate baṅkana",
-       "feed-unavailable": "Sinḍikason feed do baṅ ńamoḱkana",
+       "feed-unavailable": "ᱥᱤᱱᱰᱤᱠᱮᱥᱚᱱ ᱟᱡᱚ ᱠᱩᱫᱚ ᱵᱟᱝ ᱧᱟᱢᱚᱜ ᱠᱟᱱᱟ",
        "site-rss-feed": "$1 RSS feed",
        "site-atom-feed": " $1 ᱡᱚᱢ ᱚᱪᱚ",
        "page-rss-feed": "\"$1\" RSS feed",
        "page-atom-feed": " $1 ᱡᱚᱢ ᱚᱪᱚ",
        "red-link-title": "$1 (ᱱᱤᱭᱟᱹ ᱥᱟᱦᱴᱟ ᱫᱚ ᱵᱟᱹᱱᱩᱜ-ᱟ)",
        "sort-descending": "Ulṭạo horop lekate sajao",
-       "sort-ascending": "Horop lekate sajao",
+       "sort-ascending": "ᱥᱟᱢᱴᱟᱣ ᱛᱮ",
        "nstab-main": "ᱥᱟᱦᱴᱟ",
        "nstab-user": "ᱵᱮᱵᱦᱟᱹᱨᱤᱭᱟᱹᱜ ᱥᱟᱦᱴᱟ",
        "nstab-media": "Media ᱥᱟᱦᱴᱟ",
        "nstab-help": "ᱜᱚᱸᱲᱚ ᱥᱟᱦᱴᱟ",
        "nstab-category": "ᱛᱷᱚᱠ",
        "mainpage-nstab": "ᱢᱩᱬᱩᱛ ᱥᱟᱦᱴᱟ",
-       "nosuchaction": "Noṅkanaḱ kạmi bạnuḱa",
+       "nosuchaction": "ᱱᱚᱝᱠᱟᱱ ᱠᱟᱹᱢᱤ ᱵᱟᱹᱱᱩᱜ-ᱟ",
        "nosuchactiontext": "Noa URL re goṭa akan kạmi do ạnlekate baṅkana.\nAm do paseć mit́ṭen vul joṛaoem emakada se URL oltem vul akada.\nNoa do noṅkanaḱ menkana je {{SITENAME}} sayeṭre beoharen sofṭower re mit́ṭen vul menaḱa.",
        "nosuchspecialpage": "ᱱᱚᱝᱠᱟᱱᱟ ᱟᱥᱚᱠᱟᱭ ᱥᱟᱦᱴᱟ ᱫᱚ ᱵᱟᱹᱱᱩᱜ-ᱟ",
        "nospecialpagetext": "<strong>ᱟᱢ ᱫᱚ ᱡᱟᱦᱟᱸ ᱥᱟᱦᱴᱟ ᱞᱟᱹᱜᱤᱫ ᱮᱢ ᱱᱮᱦᱚᱨ ᱟᱠᱟᱫᱟ ᱚᱱᱟᱫᱚ ᱵᱟᱹᱱᱩᱜ-ᱟ </strong>\nᱡᱟᱦᱟᱸ ᱥᱟᱦᱴᱟᱠᱩ ᱱᱚᱸᱰᱮ ᱢᱮᱱᱟᱜ-ᱟ ᱚᱱᱟᱨᱮᱱᱟᱜ ᱛᱟᱹᱞᱠᱟᱹ ᱱᱚᱸᱰᱮᱢ ᱧᱟᱢᱟ [[Special:SpecialPages|{{int:specialpages}}]]᱾",
-       "error": "bhul",
-       "databaseerror": "á¸\8caá¹­abase vul",
+       "error": "ᱦᱩᱲᱟᱹᱜ",
+       "databaseerror": "á±°á±\9fá±´á±\9fᱵᱮᱡᱽ á±¦á±©á±²á±\9fá±¹á±\9c",
        "databaseerror-error": "ᱦᱩᱲᱟᱹᱜ: $1",
        "laggedslavemode": "'''Sontoroḱme:''' sakamre do nahaḱ nãwãnaḱko paseć bạnuḱa.",
-       "readonly": "á¸\8caá¹­abes do talagea",
+       "readonly": "á±°á±\9fá±´á±\9fᱵᱮᱡᱽ á±\9bá±\9fá±\9eá±\9fá±\9cᱮᱭá±\9f",
        "enterlockreason": "Cạbie reaḱ karon do cet́kana ma lạimẽ, Saõte tinre tala cạbim jhija ona okte hõ lạimẽ",
        "readonlytext": "Nãwã hataen ar eṭagaḱ sompadon lạgit́te ḍaṭabes do nit bondo gea. Paseć ḍaṭabes rukhiyạre niyom lekate kạmi calaḱ kana. Thoṛa ghạrịić porte laha obosthare acur hạjuḱa.\nSasetić do noa kathae roṛ keda: $1",
        "missing-article": "\"$1\" $2 noa ńutumanaḱ sakhiyạ̣t sakamre olakanaḱ do bań ṅamoka.\nNoa hoy renaḱ karon do hoyoḱkana cabak tạrik pharak se noare joṛao sakam do get́ giḍi akana.\nJudi noa do karon bań hoylen khan, noa do am sopṭoyer re kạtićtem ńam daṛeyaḱa.\nDaya katet́ noa do nonde [[Special:ListUsers/sysop|administrator]],  ṭhen lạime, URL hotete.",
        "missingarticle-rev": "(ᱥᱩᱫᱷᱨᱟᱹᱣ#:$1)",
-       "missingarticle-diff": "(Pharak: $1, $2)",
+       "missingarticle-diff": "(ᱯᱷᱟᱨᱟᱠ: $1, $2)",
        "readonly_lag": "Ḍaṭabes do aćhote tege bondo hoe akana, je lekate udhin reaḱ ḍaṭabes sarvarkor mukhiạ ḍaṭabes sarvar lekate heć daṛeaḱ.",
-       "internalerror": "Bhitri reaḱ bhul",
-       "internalerror_info": "Bhitri reaḱ vul: $1",
-       "filecopyerror": "\"$1\" rẽt khon \"$2\" rẽt baṅ kopilena.",
-       "filerenameerror": "\"$1\" rẽt reaḱ ńutum bodol kate \"$2\" em baṅ hoyoḱ kana.",
+       "internalerror": "ᱵᱷᱤᱛᱨᱤ ᱦᱩᱲᱟᱹᱜ",
+       "internalerror_info": "ᱵᱷᱤᱛᱨᱤ ᱦᱩᱲᱟᱹᱜ: $1",
+       "filecopyerror": "\"$1\" ᱨᱮᱫ ᱠᱷᱚᱱ \"$2\" ᱨᱮᱫ ᱵᱟᱝ ᱠᱚᱯᱤᱞᱮᱱᱟ᱾",
+       "filerenameerror": "\"$1\" ᱨᱮᱫ ᱨᱮᱭᱟᱜ ᱧᱩᱛᱩᱢ ᱵᱚᱫᱚᱞ ᱠᱟᱛᱮ \"$2\" ᱮᱢ ᱵᱟᱝ ᱜᱟᱱᱚᱜ ᱠᱟᱱᱟ᱾",
        "filedeleteerror": "$1 ᱨᱮᱫ ᱫᱚ ᱵᱟᱝ ᱜᱮᱫᱽ ᱜᱤᱰᱤ ᱞᱮᱱᱟ",
-       "directorycreateerror": "\"$1\" dayrekṭori do baṅ tearlena.",
-       "filenotfound": "\"$1\" rẽt do baṅ sendra ńamoḱ kana.",
-       "unexpected": "Baṅ asakan mạn: \"$1\"=\"$2\".",
-       "formerror": "Vul: forom do baṅ jimạlena.",
-       "badarticleerror": "Noa sakamre kạmiko do baṅ puraolena.",
+       "directorycreateerror": "\"$1\" ᱰᱟᱭᱨᱮᱠᱴᱳᱨᱤ ᱫᱚ ᱵᱟᱝ ᱛᱮᱭᱟᱨᱞᱮᱱᱟ᱾",
+       "filenotfound": "\"$1\" ᱨᱮᱫ ᱫᱚ ᱵᱟᱝ ᱧᱟᱢᱞᱮᱱᱟ᱾",
+       "unexpected": "ᱵᱟᱝ ᱟᱥᱟᱜ ᱠᱟᱱ ᱢᱟᱹᱱ: \"$1\"=\"$2\".",
+       "formerror": "ᱦᱩᱲᱟᱹᱜ: ᱯᱷᱚᱨᱚᱢ ᱫᱚ ᱵᱟᱝ ᱡᱤᱢᱟᱹᱞᱮᱱᱟ᱾",
+       "badarticleerror": "ᱱᱚᱣᱟ ᱠᱟᱹᱢᱤ ᱫᱚ ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟᱨᱮ ᱵᱟᱝ ᱦᱩᱭᱞᱮᱱᱟ᱾",
        "cannotdelete": "$1 sakam se rẽt do baṅ get giḍilena.\nPasec eṭaḱ hoṛ noa do lahareko get giḍi akada.",
-       "cannotdelete-title": "\"$1\" Sakam do baṅ get giḍiḱkana",
+       "cannotdelete-title": "\"$1\" ᱥᱟᱦᱴᱟ ᱫᱚ ᱵᱟᱝ ᱜᱮᱫ ᱜᱤᱰᱤ ᱟᱠᱟᱱᱟ",
        "badtitle": "ᱵᱟᱹᱨᱤᱡ ᱴᱟᱭᱴᱮᱞ",
        "badtitletext": "ᱟᱢᱮᱢ ᱱᱮᱦᱚᱨᱟᱠᱟᱱ ᱥᱟᱦᱴᱟ ᱧᱤᱛᱩᱢ ᱫᱚ ᱵᱟᱝ ᱴᱷᱤᱠᱟ, ᱠᱷᱟᱹᱞᱤ ᱥᱮ ᱵᱷᱩᱞᱜᱮ ᱵᱷᱤᱛᱨᱤ ᱯᱟᱹᱨᱥᱤᱛᱮ ᱥᱮ ᱩᱭᱠᱤ ᱴᱟᱭᱴᱮᱞ ᱛᱮ ᱡᱚᱱᱚᱲ ᱜᱮᱭᱟ᱾\nᱱᱚᱣᱟᱨᱮ ᱫᱚ ᱢᱤᱫ ᱥᱮ ᱟᱭᱢᱟ ᱩᱱᱩᱫᱩᱜ ᱢᱮᱱᱟᱜ ᱚᱠᱟ ᱫᱚ ᱧᱤᱛᱩᱢᱨᱮ ᱵᱟᱝ ᱵᱮᱵᱦᱟᱨᱚᱜ᱾",
        "querypage-no-updates": "Noa sakam reaḱ nahaḱ halot bondo gea. Nonḍe doho akana ḍaṭako do baṅ saphaḱa.",
        "viewsource": "ᱯᱷᱮᱰᱟᱛ ᱧᱮᱞ",
        "viewsource-title": "$1 ᱨᱮᱱᱟᱜ ᱯᱷᱮᱰᱟᱛ ᱧᱮᱞᱢᱮ",
-       "actionthrottled": "Kạmi reaḱ dhara bại",
+       "actionthrottled": "ᱠᱟᱹᱢᱤ ᱨᱮᱭᱟᱜ ᱫᱷᱟᱨᱟ ᱵᱟᱹᱭ",
        "protectedpagetext": "Noa sakam do ol toṅge lạgit́te do bańcao gea.",
        "viewsourcetext": "ᱱᱚᱭᱟ ᱥᱟᱦᱴᱟᱢ ᱧᱮᱞ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ ᱟᱨᱮᱢ ᱠᱚᱯᱤ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ᱾",
        "viewyourtext": "Am do '''Amaḱ sompadon''' noa sakam ńel arem kopi hatao daṛeaḱa:",
        "virus-unknownscanner": "Baṅ urum anṭvayras:",
        "welcomeuser": "ᱥᱟᱹᱜᱩᱱ ᱫᱟᱨᱟᱢ, $1!",
        "welcomecreation-msg": "Amaḱ ekaunṭ do̠ jhićena. Amaḱ pạsindko bodol alom hiṛińa.",
-       "yourname": "Beoboharicaḱ ńutum",
+       "yourname": "ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱧᱩᱛᱩᱢ:",
        "userlogin-yourname": "ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱧᱩᱛᱩᱢ",
-       "userlogin-yourname-ph": "á±\9fá±¢á±\9fá±\9c á±µá±®á±µá±¦á±\9fᱨᱤᱡ ᱧᱤᱛᱩᱢ ᱵᱚᱞᱚᱭ ᱢᱮ",
-       "createacct-another-username-ph": "ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱧᱤᱛᱩᱢ ᱵᱚᱞᱚᱭ ᱢᱮ",
+       "userlogin-yourname-ph": "á±\9fá±¢á±\9fá±\9c á±µá±®á±µá±¦á±\9fᱨᱤᱭá±\9fá±¹ ᱧᱤᱛᱩᱢ ᱵᱚᱞᱚᱭ ᱢᱮ",
+       "createacct-another-username-ph": "ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱜ ᱧᱤᱛᱩᱢ ᱵᱚᱞᱚᱭ ᱢᱮ",
        "yourpassword": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ",
        "userlogin-yourpassword": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ",
        "userlogin-yourpassword-ph": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱟᱫᱮᱨᱢᱮ",
        "createacct-yourpassword-ph": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱟᱫᱮᱨᱢᱮ",
-       "yourpasswordagain": "Arhõ oku namber olme",
+       "yourpasswordagain": "ᱫᱚᱲᱦᱟᱛᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱚᱞᱢᱮ",
        "createacct-yourpasswordagain": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱴᱷᱟᱹᱣᱠᱟᱹᱭ ᱢᱮ",
        "createacct-yourpasswordagain-ph": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱟᱫᱮᱨᱢᱮ ᱫᱚᱲᱦᱟ",
        "userlogin-remembermypassword": "ᱵᱚᱞᱚ ᱛᱷᱤᱨᱜᱮ ᱫᱚᱦᱚᱠᱟᱹᱧᱢᱮ",
-       "yourdomainname": "Amaḱ ḍomen:",
+       "yourdomainname": "ᱟᱢᱟᱜ ᱧᱩᱛᱩᱢ:",
        "externaldberror": "Hoe daṛeyaḱa jahan bahre reaḱ jacaeaḱ ḍaṭabes vul hoeakana se amaḱ bahre reaḱ ekaunṭ do nahaḱ halot aguire ạidạri bạnuḱa.",
        "login": "ᱵᱚᱞᱚᱜ ᱢᱮ",
-       "nav-login-createaccount": "Boloḱ́ duạr / ekaunt tearme",
+       "nav-login-createaccount": "ᱵᱚᱞᱚᱜ ᱫᱩᱣᱟᱹᱨ / ᱦᱤᱥᱟᱹᱵ ᱠᱷᱟᱛᱟ ᱛᱮᱭᱟᱨᱢᱮ",
        "logout": "ᱚᱰᱚᱠᱚᱜ ᱢᱮ",
        "userlogout": "ᱚᱰᱚᱠᱚᱜ ᱢᱮ",
        "notloggedin": "ᱵᱟᱢ ᱵᱚᱞᱚ ᱟᱠᱟᱱᱟ",
        "createacct-benefit-body3": "ᱱᱮᱛᱟᱨ {{PLURAL:$1|ᱮᱱᱮᱢᱤᱭᱟᱹ|ᱮᱱᱮᱢᱤᱭᱟᱹᱠᱚ}}",
        "badretype": "Am do okaṭaḱ oku nambarkom em keda ona do baṅ milạolena.",
        "userexists": "Laṛcaṛicaḱ ńutum em hoyena ona do beohar hoyakana.\nDayakatet́ eṭagaḱ ńutum bachaome.",
-       "loginerror": "Bhitri bolok do vulgea",
+       "loginerror": "ᱵᱷᱤᱛᱨᱤ ᱵᱚᱞᱚᱜ ᱦᱩᱲᱟᱹᱜ",
        "createacct-error": "ᱠᱷᱟᱛᱟ ᱛᱮᱭᱟᱨ ᱦᱩᱲᱟᱹᱜ",
-       "createaccounterror": "Ekaunṭ do baṅ tear lena: $1",
+       "createaccounterror": "ᱦᱤᱥᱟᱹᱵ ᱠᱷᱟᱛᱟ ᱵᱟᱝ ᱛᱮᱭᱟᱨᱠᱟᱱᱟ: $1",
        "nocookiesnew": "ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱦᱤᱥᱟᱹᱵ ᱛᱮᱭᱟᱨ ᱦᱩᱭ ᱞᱮᱱᱟ, ᱢᱮᱱᱠᱷᱟᱱ ᱟᱢᱫᱚ ᱱᱤᱛ ᱫᱷᱟᱹᱵᱤᱡ ᱵᱟᱢ ᱵᱚᱞᱚᱣᱠᱟᱱᱟ᱾ {{SITENAME}} ᱨᱮ ᱠᱩᱠᱤᱠᱚ ᱵᱮᱵᱦᱟᱨ ᱠᱟᱛᱮ ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱦᱤᱥᱟᱹᱵᱨᱮ ᱵᱚᱞᱚᱣᱟ᱾\nᱟᱢᱟᱜ ᱠᱩᱠᱤᱠᱚ ᱵᱚᱸᱫᱚ ᱦᱩᱭᱠᱟᱱᱟ᱾\nᱫᱟᱭᱟᱠᱟᱛᱮ ᱠᱩᱠᱤᱠᱚ ᱡᱷᱤᱡ ᱢᱮ, ᱚᱱᱟ ᱛᱟᱭᱚᱢ ᱟᱢᱟᱜ ᱱᱟᱣᱟ ᱵᱮᱵᱦᱟᱨᱤᱡ ᱧᱤᱛᱩᱢ ᱟᱨ ᱩᱠᱩ ᱮᱞᱥᱚᱝ ᱵᱮᱵᱦᱟᱨ ᱠᱟᱛᱮᱡ ᱱᱚᱣᱟ ᱦᱤᱥᱟᱹᱵᱨᱮ ᱵᱚᱞᱚᱜᱢᱮ᱾",
        "nocookieslogin": "{{SITENAME}} re kuki hotete beoharićaḱ bhitri boloḱ do hoyoḱa. Amaḱ sendrare kuki bondo menaḱa. Kuki cạlu kate arhõ kurumuṭuimẽ.",
        "nocookiesfornew": "Beoharićaḱ ekaunṭ do baṅ tear akana, Cedaḱ je noa ńamoḱ jaega babote ale do bale uruma.\nAle do baḍae ocolem amaḱ kuki doe kạmikana, sakam do arhõ rakaṕ lạgit́te kurumuṭuemẽ.",
        "passwordtooshort": "Uku nambar do {{PLURAL:$1 1 horop reaḱ $1 horop reaḱ}} mudre hoyoḱ jạruṛa.",
        "password-name-match": "Amaḱ oku nambar do amaḱ ńutum khon eṭaḱ hoyoḱ jạruṛtama.",
        "password-login-forbidden": "Noa laṛcaṛicaḱ ńutum ar oku nambar do ạnlekate baṅkana.",
-       "mailmypassword": "á±±á±\9fá±£á±\9fá±\9bá±® á±©á± á±© á±®á±\9eá±¥á±\9aá±\9d á±®á±¢á±¢á±®",
-       "passwordremindertitle": "á±±á±\9fá±£á±\9f á±±á±¤á±\9b á±\9eá±\9fá±¹á±\9cᱤá±\9b á±©á± á±© á±®á±\9eá±¥á±\9aá±\9d {{SITENAME}} ᱞᱟᱹᱜᱤᱛ ᱛᱮ",
+       "mailmypassword": "á±±á±\9fá±£á±\9fá±\9bá±® á±«á±\9fá±±á±\9fá±\9d á±¥á±\9fá±µá±\9fᱫᱽ á±®á±¢",
+       "passwordremindertitle": "á±±á±\9fá±£á±\9f á±±á±¤á±\9b á±\9eá±\9fá±¹á±\9cᱤá±\9b á±«á±\9fá±±á±\9fá±\9d á±¥á±\9fá±µá±\9fᱫᱽ {{SITENAME}} ᱞᱟᱹᱜᱤᱛ ᱛᱮ",
        "noemail": "\"$1\" beoharić lạgit́te do jahan e-mail ṭhikana rukhiyạ doho bạnuḱa.",
        "noemailcreate": "Am do mitṭen jewet e-mail ṭhikạna em jaruṛ menaḱtama.",
        "passwordsent": "\"$1\" ṭhikạnate resṭariyen e-mail lạgit́te mitṭen oku nambar em hoyena.\nDaya kate ńam porte arhõ bhitri boloḱme.",
        "invalidemailaddress": "Noa e-mail ṭhikạna do baṅ hataoa, karon noa formeṭ do pusṭạote baṅ em akana. Dayakate pusṭao formeṭte ṭhikạna emmẽ, se khet do khạliemẽ.",
        "cannotchangeemail": "Ekaunṭ e-mail ṭhikạnakodo noa wiki re baṅ bodoloḱ kana.",
        "emaildisabled": "Noa sayeṭre do e-mail em subita bạnuḱa.",
-       "accountcreated": "Ekaunṭ do teyarena",
+       "accountcreated": "ᱦᱤᱥᱟᱹᱵ ᱠᱷᱟᱛᱟ ᱛᱮᱭᱟᱨᱱᱟ",
        "accountcreatedtext": "$1 lạgit́te ekaunṭ do benaoena.",
-       "createaccount-title": "{{SITENAME}} lạgit́te ekaunṭ benao",
+       "createaccount-title": "{{SITENAME}} ᱞᱟᱹᱜᱤᱛᱛᱮ ᱦᱤᱥᱟᱹᱵ ᱠᱷᱟᱛᱟ ᱛᱮᱭᱟᱨ",
        "createaccount-text": "Okoe co am lạgit́te mitṭen ekaunṭko amaḱ e-mail ṭhikạna lạgit {{SITENAME}} re ($4) ńutum \"$2\", oku nambar \"$3\".\nAm do mesagem baṅ daṛeyaḱa, judi noa ekaunṭ do vulge benaolen khan.",
        "login-throttled": "Am do mitghạri lahare por por aema dhao boloḱem kurumuṭu keda.\nArhõ kurumuṭue lahare dayakate thoṛagan tạṅgiemẽ.",
        "login-abort-generic": "Amaḱ bhitri boloḱ do baṅ hoylena - batena.",
        "pt-createaccount": "ᱴᱷᱟᱭ ᱵᱮᱱᱟᱣᱢᱮ",
        "pt-userlogout": "ᱚᱰᱚᱠᱚᱜ ᱢᱮ",
        "user-mail-no-addy": "Jahan e-mail ṭhikana bạgi kate e-mail kul kurumuṭu hoena.",
-       "changepassword": "Uku nombor bodolme",
-       "resetpass_header": "Ekaunṭ oku namber bodol",
-       "oldpassword": "Mare uku nombor",
+       "changepassword": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱵᱚᱫᱚᱞ",
+       "resetpass_header": "ᱦᱤᱥᱟᱹᱵ ᱠᱷᱟᱛᱟ ᱨᱮᱱᱟᱜ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱵᱚᱫᱚᱞ",
+       "oldpassword": "ᱢᱟᱨᱮᱭᱟᱜ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ:",
        "newpassword": "ᱱᱟᱶᱟ ᱫᱟᱱᱟᱝᱥᱟᱵᱟᱫᱽᱺ",
-       "retypenew": "Doṛhate oku namber olme",
-       "resetpass_submit": "Oku namber joṛao ar bhitri bolok",
+       "retypenew": "ᱫᱚᱲᱦᱟᱛᱮ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱚᱞᱢᱮ:",
+       "resetpass_submit": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱮᱢᱢᱮ ᱟᱨ ᱵᱚᱞᱚᱜ ᱢᱮ",
        "changepassword-success": "Amaḱ oku namber do napayte bodolena!\nNitoḱ do am bhitritem boloḱkana...",
        "botpasswords": "ᱵᱚᱴ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ",
        "botpasswords-createnew": "ᱱᱟᱶᱟ ᱵᱚᱴ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱛᱮᱭᱟᱨᱢᱮ",
        "botpasswords-label-resetpassword": "ᱱᱟᱣᱟᱛᱮ ᱫᱟᱱᱟᱝᱥᱟᱵᱟᱫᱽ ᱮᱢ",
        "botpasswords-label-grants-column": "ᱦᱩᱭᱠᱟᱱ",
        "botpasswords-bad-appid": "ᱵᱚᱴ ᱧᱤᱛᱩᱢ \"$1\" ᱵᱟᱝ ᱴᱷᱤᱠᱟ᱾",
-       "botpasswords-created-title": "á±µá±\9aá±´ á±©á± á±© á±®á±\9eá±¥á±\9aá±\9d ᱛᱮᱭᱟᱨᱱᱟ",
+       "botpasswords-created-title": "á±µá±\9aá±´ á±©á± á±© á±«á±\9fá±±á±\9fá±\9d á±¥á±\9fá±µá±\9fᱫᱽ ᱛᱮᱭᱟᱨᱱᱟ",
        "botpasswords-updated-title": "ᱵᱚᱴ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱩᱛᱷᱱᱟᱹᱣ",
-       "botpasswords-deleted-title": "á±µá±\9aá±´ á±©á± á±© á±®á±\9eá±¥á±\9aá±\9d ᱢᱩᱪᱷᱟᱹᱣᱱᱟ",
-       "resetpass_forbidden": "Oku namber do baṅ bodoloklena",
+       "botpasswords-deleted-title": "á±µá±\9aá±´ á±«á±\9fá±±á±\9fá±\9d á±¥á±\9fá±µá±\9fᱫᱽ ᱢᱩᱪᱷᱟᱹᱣᱱᱟ",
+       "resetpass_forbidden": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱵᱟᱝ ᱵᱚᱫᱚᱞᱜ-ᱟ",
        "resetpass_forbidden-reason": "ᱩᱠᱩ ᱮᱞᱥᱚᱝ ᱵᱟᱝ ᱵᱚᱫᱚᱞᱚᱜ-ᱟ: $1",
        "resetpass-no-info": "Noa sakam sojhete laṛcaṛ lạgit́te am do bhitri boloḱ hoyoḱtama.",
-       "resetpass-submit-loggedin": "Oku namber bodol",
-       "resetpass-submit-cancel": "Bạgi",
-       "resetpass-temp-password": "Nit lạgit uku nambar:",
-       "passwordreset": "á±±á±\9fá±£á±\9fá±\9bá±® á±©á± á±© á±®á±\9eá±¥á±\9aá±\9d á±®á±¢á±¢á±®",
+       "resetpass-submit-loggedin": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱵᱚᱫᱚᱞ",
+       "resetpass-submit-cancel": "ᱵᱟᱫᱽ",
+       "resetpass-temp-password": "ᱱᱮᱛᱚᱜ ᱞᱟᱹᱜᱤᱛ ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ:",
+       "passwordreset": "á±±á±\9fá±£á±\9fá±\9bá±® á±«á±\9fá±±á±\9fá±\9d á±¥á±\9fá±µá±\9fᱫᱽ á±®á±¢",
        "passwordreset-disabled": "Noa wikire amaḱ uku nambar nãwãte em lạgit subita do bando gea.",
-       "passwordreset-username": "Beoharicaḱ ńutum:",
-       "passwordreset-domain": "á¸\8comen:",
+       "passwordreset-username": "ᱵᱮᱵᱦᱟᱨᱤᱡ ᱧᱩᱛᱩᱢ:",
+       "passwordreset-domain": "ᱧᱩá±\9bᱩᱢ:",
        "passwordreset-email": "ᱤᱢᱮᱞ ᱵᱩᱴᱟᱹ:",
        "passwordreset-emailtitle": "{{SITENAME}} sayeṭre beoharićaḱ purạo thutiko",
        "passwordreset-emailelement": "ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱧᱩᱛᱩᱢ: \n$1\n\nᱢᱤᱫ ᱜᱷᱟᱹᱲᱤ ᱞᱟᱹᱜᱤᱛ ᱫᱟᱱᱟᱝ ᱱᱟᱵᱟᱫᱽ: \n$2",
        "passwordreset-emailsentemail": "Mitṭen disạ ruaṛ e-mail do kulena.",
-       "changeemail": "email ᱴᱷᱤᱠᱱᱟ ᱵᱚᱫᱚᱞ ᱢᱮ ᱥᱮ ᱚᱪᱚᱜᱽ ᱢᱮ",
+       "changeemail": "ᱤᱢᱮᱞ ᱴᱷᱤᱠᱱᱟ ᱵᱚᱫᱚᱞ ᱢᱮ ᱥᱮ ᱚᱪᱚᱜᱽ ᱢᱮ",
        "changeemail-header": "Ekaunṭ e-mail ṭhikạna do bodolme",
        "changeemail-no-info": "Noa sakam sojhete laṛcaṛ lạgit́te am do bhitri boloḱ hoyoḱtama.",
-       "changeemail-oldemail": "ᱱᱮᱛᱚᱜ-ᱟᱜ email ᱴᱷᱤᱠᱟᱹᱱᱟ",
+       "changeemail-oldemail": "ᱱᱮᱛᱚᱜ-ᱟᱜ ᱤᱢᱮᱞ ᱴᱷᱤᱠᱟᱹᱱᱟ",
        "changeemail-newemail": "ᱱᱟᱣᱟ ᱤᱢᱮᱞ ᱵᱩᱴᱟᱹ:",
        "changeemail-none": "(ᱪᱮᱫ ᱦᱚᱸ ᱵᱟᱹᱱᱩᱜ-ᱟ)",
-       "changeemail-password": "á±\9fá±¢á±\9fá±\9c {{SITENAME}} á±©á± á±© á±®á±\9eá±¥á±\9aá±\9d:",
+       "changeemail-password": "á±\9fá±¢á±\9fá±\9c {{SITENAME}} á±«á±\9fá±±á±\9fá±\9d á±¥á±\9fá±µá±\9fá±½:",
        "changeemail-submit": "E-mail ᱵᱚᱫᱚᱞᱢᱮ",
        "bold_sample": "ᱢᱚᱴᱟ ᱚᱞ",
        "bold_tip": "ᱢᱚᱴᱟ ᱚᱞ",
        "blockedtitle": "ᱵᱮᱵᱦᱟᱨᱤᱡ ᱫᱚ ᱮᱥᱮᱛ ᱚᱪᱚᱟᱠᱟᱱᱟ",
        "blockedtext": "<strong>ᱟᱢᱟᱜ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱧᱩᱛᱩᱢ ᱟᱨᱵᱟᱝ IP ᱵᱩᱴᱟᱹ ᱫᱚ ᱵᱚᱸᱫᱽ ᱟᱠᱟᱱᱟ ᱾ </strong>\n\nᱱᱚᱶᱟ ᱵᱚᱸᱫᱽ ᱫᱚ $1 ᱫᱟᱨᱟᱭᱛᱮ ᱦᱩᱭᱟᱠᱱᱟ ᱾\nᱱᱚᱶᱟ ᱨᱮᱱᱟᱜ ᱚᱡᱮ ᱫᱚ ᱮᱢᱮᱱᱟ <em>$2</em>.\n\n* ᱵᱚᱸᱫᱽ ᱮᱦᱚᱵ: $8\n* ᱵᱚᱸᱫᱽ ᱢᱩᱪᱟᱹᱫ: $6\n* ᱟᱥᱟᱦᱟᱱ ᱵᱚᱸᱫᱽᱠᱚ: $7\n\nᱟᱢ $1 ᱮᱢ ᱥᱟᱹᱜᱟᱹᱭ ᱫᱟᱲᱮᱭᱟᱭᱟ ᱵᱟᱝᱠᱷᱟᱱ ᱮᱴᱟᱜ [[{{MediaWiki:Grouppage-sysop}}|ᱟᱰᱢᱤᱱᱤᱥᱴᱨᱮᱴᱚᱨ]] ᱵᱚᱸᱫᱽ ᱵᱟᱵᱚᱫᱽ ᱛᱮ ᱜᱟᱞᱚᱪ ᱞᱟᱹᱜᱤᱫ ᱾\nᱟᱢ ᱵᱟᱢ ᱵᱮᱵᱷᱟᱨ ᱫᱟᱲᱮᱭᱟᱜ \"email this user\" ᱥᱩᱵᱤᱫᱷᱟ ᱡᱚᱛᱷᱟᱛ ᱤᱢᱮᱞ ᱵᱩᱴᱟᱹ ᱛᱟᱢ ᱵᱟᱝ ᱛᱟᱦᱮᱸᱱ ᱠᱷᱟᱱ ᱟᱨ ᱱᱚᱶᱟ ᱫᱚ ᱪᱤᱱᱦᱟᱹᱣ-ᱟ [[Special:Preferences|ᱠᱷᱟᱛᱟ ᱧᱮᱞᱚᱚᱜ]] ᱠᱷᱚᱱ ᱟᱨ ᱟᱢ ᱫᱚ ᱵᱟᱢ ᱵᱚᱸᱫᱽ ᱟᱠᱟᱱᱟ ᱱᱚᱶᱟ ᱵᱮᱵᱷᱟᱨ ᱠᱷᱚᱱ ᱾\nᱟᱢᱟᱜ ᱱᱤᱛᱚᱜᱟᱜ IP ᱵᱩᱴᱟᱹ ᱫᱚ $3, ᱟᱨ ᱵᱚᱸᱫᱽ ID ᱫᱚ #$5  \nᱫᱟᱭᱟᱠᱟᱛᱮ ᱥᱮᱞᱮᱫᱽ ᱢᱮ ᱪᱮᱛᱟᱱᱟᱜ ᱠᱟᱛᱷᱟᱠᱚ ᱡᱚᱛᱚ ᱞᱮᱠᱟᱱ ᱠᱩᱠᱞᱤ ᱨᱮ ᱾",
        "blockednoreason": "ᱡᱟᱸᱦᱟᱸᱱ ᱚᱡᱮ ᱵᱟᱝ ᱮᱢᱠᱟᱱᱟ",
-       "whitelistedittext": "Sakamre sompadon lạgit́te $1 em hoyoḱa.",
-       "nosuchsectiontitle": "Pahaṭa bȧn ńamlena",
+       "whitelistedittext": "ᱥᱟᱦᱴᱟ ᱥᱟᱯᱲᱟᱣ ᱞᱟᱹᱜᱤᱛ $1 ᱮᱢ ᱦᱩᱭᱩᱜ-ᱟ᱾",
+       "nosuchsectiontitle": "ᱛᱷᱟᱠ ᱵᱟᱝ ᱧᱟᱢᱞᱮᱱᱟ",
        "loginreqtitle": "ᱵᱚᱞᱚᱜ ᱡᱟᱹᱨᱩᱲᱟ",
        "loginreqlink": "ᱵᱚᱞᱚᱜ ᱢᱮ",
        "loginreqpagetext": "Eṭagaḱ sakamko ńel lạgit́te do am $1 hoyoḱ jạruṛtama.",
-       "accmailtitle": "Uku nambar do kulena.",
+       "accmailtitle": "ᱫᱟᱱᱟᱝ ᱥᱟᱵᱟᱫᱽ ᱠᱩᱞ ᱦᱩᱭᱱᱟ",
        "accmailtext": "[[User talk:$1 $1]] lạgit́te aćte benaoen uku nambar do $2 kul hoena.\nBhitri bolo kateḱ noa nãwã ekaunṭ lạgit uku nambar \"[[Special:ChangePassword Change password]]\" sakam khonem bodol daṛyakya.",
        "newarticle": "(ᱱᱟᱣᱟᱱᱟᱜ)",
        "newarticletext": "ᱟᱢ ᱚᱠᱟ ᱥᱟᱦᱴᱟ ᱨᱮᱱᱟᱜ ᱡᱚᱱᱟᱲᱮᱢ ᱯᱟᱸᱡᱟᱸ ᱟᱹᱜᱩᱭᱫᱟ ᱚᱱᱚ ᱫᱚ ᱵᱟᱱᱩᱜ-ᱟ᱾\nᱚᱱᱟ ᱥᱟᱦᱴᱟ ᱛᱮᱭᱟᱨ ᱞᱟᱹᱜᱤᱛ ᱛᱮ, ᱞᱟᱛᱟᱨ ᱵᱟᱠᱥᱚ ᱵᱷᱤᱛᱨᱤᱨᱮ ᱚᱞ ᱮᱦᱚᱵ ᱢᱮ (ᱟᱨᱦᱚᱸ ᱡᱟᱹᱥᱛᱤ ᱵᱟᱰᱟᱭ ᱞᱟᱹᱜᱤᱛᱴᱮ [$1 ᱜᱚᱸᱲᱚᱸ ᱥᱟᱦᱴᱟ] ᱯᱟᱸᱡᱚᱸᱭᱢᱮ)᱾\nᱟᱢ ᱵᱷᱩᱞᱛᱮ ᱱᱚᱸᱰᱮᱢ ᱦᱮᱡ ᱟᱠᱟᱱ ᱠᱷᱟᱡ, ᱟᱢᱟᱜ ᱵᱨᱟᱣᱡᱟᱨ ᱨᱮᱱᱟᱜ '''ᱛᱟᱭᱚᱢ''' ᱵᱟᱴᱚᱱ ᱞᱤᱱᱢᱮ᱾",
        "noarticletext-nopermission": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟᱨᱮ ᱱᱤᱛᱚᱜ ᱪᱮᱫᱜᱮ ᱚᱞ ᱵᱟᱹᱱᱩᱜ-ᱟ᱾\n\nᱟᱢ [[Special:Search/{{PAGENAME}}|ᱱᱚᱭᱟ ᱥᱟᱦᱴᱟᱨᱮᱱᱟᱜ ᱧᱤᱛᱩᱢᱮᱢ ᱥᱮᱸᱫᱽᱨᱟ ᱫᱟᱲᱮᱭᱟᱜ-ᱟ]] ᱮᱴᱟᱜ ᱥᱟᱦᱴᱟ ᱠᱚᱨᱮᱦᱚᱸ,\nᱟᱨᱵᱟᱝ <span class=\"plainlinks\">[{{fullurl:{{#Special:Log}}|page={{FULLPAGENAMEE}}}} search the related logs]</span>.",
        "userpage-userdoesnotexist": "\"<nowiki>$1</nowiki>\" ńutuman jahãe beoharićaḱ ekaunṭ do baṅ resṭri hoeakana. Daya kate biḍạo katet́ ńelmẽ noa sakam do benoa/sompadonem menet́ kana se baṅ.",
        "userpage-userdoesnotexist-view": "ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ \"$1\" ᱮᱠᱟᱣᱱᱴ ᱫᱚ ᱵᱟᱝ ᱨᱮᱥᱴᱨᱤ ᱟᱠᱟᱱᱟ᱾",
-       "blocked-notice-logextract": "Nui beoharić do nitoḱe esetgea.\nRefarens lạgit́te nahaḱ boloḱ do latare em hoena:",
+       "blocked-notice-logextract": "ᱱᱩᱭ ᱵᱮᱵᱦᱟᱨᱤᱡ ᱫᱚ ᱱᱮᱛᱚᱜ ᱮ ᱥᱮᱥᱫᱜᱮᱭᱟ᱾\nᱨᱮᱯᱷᱟᱨᱮᱱᱥ ᱞᱟᱹᱜᱤᱛᱛᱮ ᱱᱟᱣᱟᱱᱟᱜ ᱵᱚᱞᱚᱜ ᱠᱩᱨᱩᱢᱩᱴᱩ ᱞᱟᱛᱟᱨᱨᱮ ᱮᱢ ᱦᱩᱭᱱᱟ:",
        "clearyourcache": "<strong>Note:</strong> After saving, you may have to bypass your browser's cache to see the changes.\n* <strong>Firefox / Safari:</strong> Hold <em>Shift</em> while clicking <em>Reload</em>, or press either <em>Ctrl-F5</em> or <em>Ctrl-R</em> (<em>⌘-R</em> on a Mac)\n* <strong>Google Chrome:</strong> Press <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> on a Mac)\n* <strong>Internet Explorer:</strong> Hold <em>Ctrl</em> while clicking <em>Refresh</em>, or press <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Go to <em>Menu → Settings</em> (<em>Opera → Preferences</em> on a Mac) and then to <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "updated": "(ᱦᱟᱞᱚᱛ ᱜᱮᱭᱟ)",
-       "note": "'''Noṭ:'''",
+       "note": "'''ᱱᱳᱴ:'''",
        "previewnote": "'''ᱠᱷᱮᱭᱟᱞᱢᱮ, ᱱᱚᱣᱟ ᱫᱚ ᱮᱠᱮᱱ ᱧᱮᱞᱚᱜ ᱞᱟᱹᱜᱤᱛ᱾'''\nᱟᱢᱟᱜ ᱵᱚᱫᱚᱞ ᱠᱚᱫᱚ ᱱᱤᱛ ᱫᱷᱟᱹᱵᱤᱡ ᱵᱟᱝ ᱨᱩᱠᱷᱤᱭᱟᱹᱣᱠᱟᱱᱟ!",
        "continue-editing": "ᱥᱟᱯᱲᱟᱣ ᱡᱟᱜᱟ ᱪᱟᱞᱟᱜ ᱢᱮ",
        "editing": "ᱥᱟᱯᱲᱟᱣ ᱪᱟᱞᱟᱜᱠᱟᱱᱟ $1",
        "template-semiprotected": "(ᱦᱩᱰᱤᱧ ᱨᱩᱠᱷᱤᱭᱟᱹ)",
        "hiddencategories": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱫᱚ {{PLURAL:$1 1 ᱩᱠᱩ ᱛᱷᱚᱠ|$1 ᱩᱠᱩ ᱛᱷᱚᱠᱠᱩ}} ᱜᱟᱶᱛᱟᱨᱮᱱᱜᱮ:",
        "nocreate-loggedin": "ᱱᱟᱣᱟ ᱥᱟᱦᱴᱟ ᱛᱮᱭᱟᱨ ᱞᱟᱹᱜᱤᱛᱛᱮ ᱟᱢᱟᱜ ᱟᱹᱭᱫᱟᱹᱨᱤ ᱫᱚ ᱵᱟᱱᱩᱜ-ᱟ᱾",
-       "sectioneditnotsupported-title": "Pahaṭa sompadona do bae hataoeda",
-       "sectioneditnotsupported-text": "Noa sompadona sakamre pahaṭa sompadona do bae hataoeda",
+       "sectioneditnotsupported-title": "ᱛᱷᱟᱠ ᱥᱟᱯᱲᱟᱣ ᱵᱟᱭ ᱦᱟᱛᱟᱣᱫᱟ",
+       "sectioneditnotsupported-text": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟᱨᱮ ᱛᱷᱟᱠ ᱥᱟᱯᱲᱟᱣ ᱵᱟᱭ ᱦᱟᱛᱟᱣᱫᱟ᱾",
        "permissionserrors": "ᱟᱹᱭᱫᱟᱹᱨᱤ ᱦᱩᱲᱟᱹᱜ",
-       "permissionserrorstext": "Noa kạmi amaḱ ạidạri do banuḱa, {{PLURAL:$1 gan karon reaḱ gan karon reaḱ}} lạgit:",
+       "permissionserrorstext": "ᱱᱚᱣᱟ ᱠᱟᱹᱢᱤᱨᱮ ᱟᱢᱟᱜ ᱟᱹᱭᱫᱟᱹᱨᱤ ᱵᱟᱹᱱᱩᱜ-ᱟ, {{PLURAL:$1 ᱱᱚᱣᱟ ᱞᱟᱹᱜᱤᱛ }}:",
        "permissionserrorstext-withaction": "ᱟᱢᱟᱜ $2 ᱠᱟᱹᱢᱤᱨᱮ ᱟᱹᱭᱫᱟᱹᱨᱤ ᱫᱚ ᱵᱟᱱᱩᱜ-ᱟ, ᱚᱱᱟ ᱨᱮᱭᱟᱜ {{PLURAL:$1 ᱠᱟᱨᱚᱱ/ ᱠᱟᱨᱚᱱᱠᱚ}}:",
        "recreate-moveddeleted-warn": "'''ᱥᱚᱱᱛᱚᱨᱚᱜᱢᱮ: ᱟᱢ ᱫᱚ ᱟᱨᱦᱚᱸ ᱫᱚᱲᱦᱟᱛᱮ ᱥᱟᱦᱴᱟᱢ ᱛᱮᱭᱟᱨᱫᱟ ᱡᱟᱸᱦᱟ ᱫᱚ ᱞᱟᱦᱟᱨᱮᱜᱮ ᱜᱮᱫ ᱜᱤᱰᱤᱭᱟᱠᱟᱱᱟ᱾\nᱟᱢ ᱫᱚ ᱜᱩᱱᱟᱹᱱᱢᱮ ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱥᱟᱯᱲᱟᱣ ᱠᱟᱹᱢᱤ ᱪᱟᱹᱞᱩ ᱜᱟᱱᱚᱜ-ᱟ ᱥᱮ ᱵᱟᱝ᱾\nᱱᱚᱣᱟ ᱜᱮᱫ ᱜᱤᱰᱤ ᱟᱨ ᱛᱟᱞᱟ ᱚᱪᱚᱜ ᱥᱟᱦᱴᱟ ᱱᱚᱸᱰᱮ ᱮᱢ ᱦᱩᱭᱱᱟ ᱫᱷᱚᱠ ᱞᱟᱹᱜᱤᱛᱛᱮ᱾",
        "moveddeleted-notice": "ᱱᱚᱭᱟ ᱥᱟᱦᱴᱟ ᱫᱚ ᱜᱮᱫ ᱜᱤᱰᱤ ᱦᱩᱭ ᱟᱠᱟᱱᱟ᱾\nᱜᱮᱫ ᱥᱮ ᱵᱟᱸᱪᱟᱣ ᱥᱮ ᱚᱪᱚᱜ ᱜᱤᱰᱤᱭᱟᱠᱟᱱ ᱥᱟᱦᱴᱟ ᱨᱮᱱᱟᱜ ᱥᱟᱹᱠᱷᱤ ᱞᱮᱠᱟᱛᱮ ᱞᱟᱛᱟᱨᱨᱮ ᱮᱢ ᱦᱩᱭᱱᱟ᱾",
-       "log-fulllog": "Joto cạbi udugmẽ",
-       "edit-hook-aborted": "Huk hotete joto sompadonko bạgi hoeakana.\nNoa reaḱ jahan katha do bạnuḱa.",
+       "log-fulllog": "ᱯᱩᱨᱟᱹ ᱪᱟᱹᱵᱤ ᱛᱟᱹᱞᱠᱟᱹ ᱩᱫᱩᱜᱽᱢᱮ",
+       "edit-hook-aborted": "ᱦᱩᱠ ᱦᱚᱛᱮᱛ ᱡᱚᱛᱚ ᱥᱟᱯᱲᱟᱣ ᱵᱟᱹᱜᱤ ᱦᱩᱭᱠᱟᱱᱟ᱾\nᱱᱚᱣᱟ ᱨᱮᱭᱟᱜ ᱡᱟᱸᱦᱟᱸ ᱠᱟᱛᱷᱟ ᱵᱟᱹᱱᱩᱜ-ᱟ᱾",
        "edit-gone-missing": "ᱥᱟᱦᱴᱟ ᱫᱚ ᱵᱟᱝ ᱦᱟᱞᱚᱛ ᱨᱩᱭᱟᱹᱲᱞᱮᱱᱟ᱾\nᱯᱟᱥᱮᱡᱽ ᱫᱚ ᱚᱪᱚᱜ ᱦᱩᱭ ᱟᱠᱟᱱᱟ᱾",
        "edit-conflict": "ᱥᱟᱯᱲᱟᱣ ᱵᱟᱹᱭᱨᱤ᱾",
-       "edit-no-change": "Amaḱ sompadon do baṅ hataolena, Cedaḱ je olre jahan bodol bạnuḱa.",
+       "edit-no-change": "ᱟᱢᱟᱜ ᱥᱟᱯᱲᱟᱣ ᱵᱟᱝ ᱦᱟᱛᱟᱣᱞᱮᱱᱟ ᱡᱮᱫᱟᱜ ᱡᱮ ᱚᱞᱨᱮ ᱡᱟᱸᱦᱟᱸᱱ ᱵᱚᱫᱚᱞ ᱵᱟᱹᱱᱩᱜ-ᱟ᱾",
        "postedit-confirmation-created": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱫᱚ ᱛᱮᱭᱟᱨᱠᱟᱱᱟ᱾",
        "postedit-confirmation-restored": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱫᱚ ᱟᱹᱜᱩ ᱨᱩᱣᱟᱹᱲ ᱦᱩᱭᱠᱟᱱᱟ᱾",
        "edit-already-exists": "ᱱᱟᱣᱟ ᱥᱟᱦᱴᱟ ᱵᱟᱝ ᱛᱮᱭᱟᱨ ᱞᱮᱱᱟ᱾\nᱱᱚᱣᱟ ᱫᱚ ᱞᱟᱦᱟ ᱠᱷᱚᱱ ᱢᱮᱱᱟᱜ ᱜᱮᱭᱟ᱾",
        "post-expand-template-argument-category": "Pages containing omitted template arguments",
        "undo-failure": "ᱥᱟᱯᱲᱟᱣᱠᱚ ᱵᱟᱭ ᱟᱹᱪᱩᱨ ᱨᱩᱣᱟᱹᱲᱚᱜ-ᱟ ᱛᱟᱞᱟ-ᱢᱟᱞᱟ ᱥᱟᱯᱲᱟᱣ ᱵᱤᱨᱚᱫᱽ ᱤᱫᱤᱠᱟᱛᱮ |",
        "viewpagelogs": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱨᱮᱭᱟᱜ ᱞᱚᱜᱽᱠᱚ ᱧᱮᱞᱢᱮ",
-       "nohistory": "Noa sakam re do jahan sompadon reaḱ jạṛ bạnuḱa.",
+       "nohistory": "ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱨᱮᱱᱟᱜ ᱥᱟᱯᱲᱟᱣ ᱱᱟᱜᱟᱢ ᱵᱟᱹᱱᱩᱜ-ᱟ᱾",
        "currentrev": "ᱱᱮᱛᱟᱨ ᱧᱮᱞ",
        "currentrev-asof": "ᱱᱟᱣᱟ ᱧᱮᱞ ᱞᱮᱠᱟᱛᱮ $1",
        "revisionasof": "Revision as of $1",
        "rev-deleted-comment": "(ᱥᱟᱯᱲᱟᱣ ᱢᱩᱬᱩᱛ ᱠᱟᱛᱦᱟ ᱚᱪᱟᱜᱠᱟᱱᱟ)",
        "rev-deleted-user": "(ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱧᱩᱛᱩᱢ ᱚᱪᱚᱜᱠᱟᱱᱟ)",
        "rev-deleted-event": "(Log kạmi do ocog hoena)",
-       "rev-deleted-user-contribs": "[Beoharićaḱ ńutum se IP ṭhikạna do ocog hoena - kạmi khon sompadon do uku hoe akana]",
+       "rev-deleted-user-contribs": "[ᱵᱮᱵᱦᱟᱨᱤᱡ ᱧᱩᱛᱩᱢ ᱥᱮ IP ᱴᱷᱤᱠᱟᱱᱟ ᱫᱚ ᱚᱪᱟᱜ ᱦᱩᱭᱱᱟ - ᱮᱱᱮᱢ ᱠᱷᱚᱱ ᱥᱟᱯᱲᱟᱣ ᱫᱚ ᱩᱠᱩ ᱟᱠᱟᱱᱟ]",
        "rev-delundel": "ᱧᱮᱧᱮᱞᱟᱜ ᱵᱚᱫᱚᱞ",
        "rev-showdeleted": "ᱥᱚᱫᱚᱨ",
        "revisiondelete": "ᱜᱮᱫ ᱜᱤᱰᱤ/ᱵᱟᱝ ᱜᱮᱫ ᱜᱤᱰᱤ ᱥᱩᱫᱷᱨᱟᱹᱣᱠᱚ",
        "timezoneregion-australia": "ᱚᱥᱴᱨᱮᱞᱤᱭᱟ",
        "prefs-searchoptions": "ᱥᱮᱸᱫᱽᱨᱟ",
        "prefs-files": "ᱨᱮᱫᱠᱚ",
-       "youremail": "E-mail:",
+       "youremail": "Email:",
        "username": "Beoharićaḱ ńutum:",
        "yourrealname": "ᱟᱥᱚᱞ ᱧᱩᱛᱩᱢ:",
        "yourlanguage": "ᱯᱟᱹᱨᱥᱤ:",
        "yournick": "ᱱᱟᱶᱟ ᱥᱩᱦᱤ:",
        "gender-male": "Baba hoṛ",
        "gender-female": "Gogo hoṛ, Kuṛi, Kuṛi gidrạ",
-       "email": "E-mail",
+       "email": "Email",
        "prefs-help-email": "E-mail ṭhikana do bạṛtitege, menkhan uku namber nãwãte benao jạruṛa, am do amaḱ uku nomborem hiṛiń keda.",
        "prefs-help-email-others": "Am são e-mail hotete jogajog dohoy lạgitte mitṭen joṛao se amaḱ katha roṛaḱ sakam bachao jońme.\nAmaḱ e-mail ṭhikạna do bań cabaḱa tinre onko do ko beohara",
        "prefs-signature": "ᱥᱩᱦᱤ",
        "right-read": "ᱥᱟᱦᱴᱟᱠᱩ ᱯᱟᱲᱦᱟᱣᱢᱮ",
        "right-edit": "ᱥᱟᱦᱴᱟᱠᱩ ᱥᱟᱯᱲᱟᱣ",
        "right-createpage": "Sakamko benoamẽ (Okako do galmarao sakamko baṅkan)",
-       "right-createtalk": "Galmarao sakamko benaomẽ",
+       "right-createtalk": "ᱜᱟᱞᱢᱟᱨᱟᱣ ᱥᱟᱦᱴᱟᱠᱩ ᱵᱮᱱᱟᱣᱢᱮ",
        "right-createaccount": "ᱱᱟᱶᱟ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹ ᱠᱷᱟᱛᱟ ᱛᱮᱭᱟᱨ",
        "right-move": "ᱥᱟᱦᱴᱟᱠᱚ ᱥᱟᱦᱟᱭᱢᱮ",
        "right-move-subpages": "ᱥᱟᱦᱴᱟ ᱥᱟᱦᱟᱦᱟᱭᱢᱮ ᱥᱟᱶᱛᱮᱱ ᱥᱟᱦᱴᱟᱠᱚ ᱥᱟᱶ",
        "right-movefile": "ᱨᱮᱫᱠᱚ ᱚᱪᱚᱜᱽ ᱢᱮ",
-       "right-upload": "Rẽtko rakabmẽ",
+       "right-upload": "ᱨᱮᱫ ᱠᱚ ᱞᱟᱫᱮᱢᱮ",
        "right-writeapi": "ᱚᱞ API ᱨᱮᱱᱟᱜ ᱵᱮᱵᱷᱟᱨ",
        "right-delete": "ᱥᱟᱦᱴᱟᱠᱚ ᱜᱮᱫᱽ ᱢᱮ",
        "right-browsearchive": "ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ ᱜᱮᱫ ᱟᱠᱟᱱᱟ ᱥᱟᱦᱴᱟᱠᱚ",
        "listgrouprights-rights": "ᱟᱹᱭᱫᱟᱹᱨᱤᱠᱚ",
        "listgrouprights-helppage": "Goṛo:Gaõta ạidạri",
        "listgrouprights-members": "(ᱥᱚᱦᱮᱫᱠᱩᱣᱟᱜ ᱛᱟᱹᱞᱠᱟᱹ)",
-       "listgrouprights-addgroup-all": "Joto gaõtare ko soṅgekom",
-       "listgrouprights-removegroup-all": "Joto gaõtaren ko ocoḱgiḍikom",
+       "listgrouprights-addgroup-all": "ᱡᱚᱛᱚ ᱜᱟᱶᱛᱟᱠᱩ ᱥᱮᱞᱮᱫ ᱠᱩ ᱢᱮ",
+       "listgrouprights-removegroup-all": "ᱡᱚᱛᱚ ᱜᱟᱶᱛᱟᱠᱩ ᱚᱪᱚᱜ ᱠᱩ ᱢᱮ",
        "emailuser": "ᱱᱩᱭ ᱵᱮᱵᱦᱟᱨᱤᱡ e-mail ᱮᱢᱟᱭᱢᱮ",
-       "noemailtitle": "E-mail á¹­hikạna do banuḱa",
+       "noemailtitle": "E-mail á±´á±·á±¤á± á±\9fᱹᱱá±\9f á±«á±\9a á±µá±\9fᱹᱱᱩá±\9c-á±\9f",
        "emailusername": "ᱵᱮᱵᱦᱟᱨᱤᱡ ᱧᱩᱛᱩᱢᱺ",
        "emailusernamesubmit": "ᱮᱢ",
        "emailfrom": "ᱠᱩᱞᱤᱪ:",
        "changed": "Bodolena",
        "deletepage": "ᱥᱟᱦᱴᱟ ᱜᱮᱫᱽ ᱢᱮ",
        "delete-legend": "ᱜᱮᱫ ᱜᱤᱰᱤ",
-       "actioncomplete": "kami Chabae-ena",
-       "actionfailed": "Kami bang hoe-lena",
+       "actioncomplete": "ᱠᱟᱹᱢᱤ ᱪᱟᱵᱟᱭᱱᱟ",
+       "actionfailed": "ᱠᱟᱹᱢᱤ ᱵᱟᱝ ᱦᱩᱭᱞᱮᱱᱟ",
        "dellogpage": "ᱢᱩᱪᱷᱟᱹᱣ ᱪᱟᱹᱵᱤ",
        "rollbacklink": "ᱜᱷᱩᱨᱞᱟᱹᱣ ᱟᱹᱪᱩᱨ",
        "rollbacklinkcount": "ᱜᱚᱰᱟᱣ ᱨᱩᱣᱟᱹᱲ $1 {{PLURAL:$1|ᱥᱟᱯᱲᱟᱣ|ᱥᱟᱯᱲᱟᱣᱠᱚ}}",
        "protectedarticle": "ᱨᱩᱠᱷᱤᱭᱟᱹᱜᱮᱭᱟ \"[[$1]]\"",
        "modifiedarticleprotection": "\"[[$1]]\" ᱞᱟᱹᱜᱤᱫ ᱨᱩᱠᱷᱭᱟᱹ ᱟᱲᱮ ᱵᱚᱫᱚᱞᱮᱱᱟ",
        "protectcomment": "ᱚᱡᱮ:",
-       "protectexpiry": "Cabaḱ",
+       "protectexpiry": "ᱢᱮᱭᱟᱫ ᱯᱟᱨᱚᱢ:",
        "protect-default": "ᱡᱚᱛᱚ ᱵᱮᱵᱷᱟᱨᱤᱭᱟᱹᱠᱚ ᱫᱟᱣ ᱮᱢ",
        "restriction-edit": "ᱥᱟᱯᱲᱟᱣ",
        "restriction-move": "ᱚᱪᱚᱜ",
        "ipblocklist": "ᱵᱮᱵᱦᱟᱨᱤᱡ ᱮᱥᱮᱫᱜᱮᱭᱟᱭ",
        "ipblocklist-submit": "ᱥᱮᱸᱫᱽᱨᱟ",
        "infiniteblock": "ᱚᱦᱤᱥᱟᱹᱵᱽ",
-       "emailblock": "E-mail do esetgea",
+       "emailblock": "E-mail ᱵᱚᱸᱫᱷ ᱜᱮᱭᱟ",
        "blocklink": "ᱮᱥᱮᱫᱽ",
        "unblocklink": "ᱵᱟᱝ ᱮᱥᱮᱫ",
-       "change-blocklink": "block judạ",
+       "change-blocklink": "block ᱵᱚᱫᱚᱞ",
        "contribslink": "ᱮᱱᱮᱢ",
-       "emaillink": "E-mail kulmẽ",
+       "emaillink": "E-mail ᱠᱩᱞᱢᱮ",
        "blocklogpage": "ᱠᱩᱞᱩᱯ ᱮᱥᱮᱫ",
        "blocklogentry": "ᱮᱥᱮᱫ [[$1]] ᱥᱟᱶᱛᱮ ᱪᱟᱵᱟᱜ ᱚᱠᱛᱚ ᱚᱠᱟ ᱫᱚ $2 $3",
        "reblock-logentry": "ᱵᱚᱫᱚᱞᱮᱱᱟ ᱵᱚᱸᱫ ᱥᱟᱡᱟᱣᱠᱚ [[$1]] ᱞᱟᱹᱜᱤᱫ ᱪᱟᱵᱟᱜ ᱚᱠᱛᱚ $2 $3 ᱥᱟᱶ",
        "block-log-flags-nocreate": "ᱮᱠᱟᱣᱱᱴ ᱵᱮᱱᱟᱣ ᱵᱚᱱᱫᱷ ᱜᱮᱭᱟ",
-       "block-log-flags-noemail": "E-mail do esetgea",
-       "block-log-flags-hiddenname": "Beoharićaḱ ńutum do ukugea",
+       "block-log-flags-noemail": "E-mail ᱵᱚᱸᱫᱷ ᱜᱮᱭᱟ",
+       "block-log-flags-hiddenname": "ᱵᱮᱵᱦᱟᱨᱤᱭᱟᱜ ᱧᱩᱛᱩᱢ ᱩᱠᱩ ᱜᱮᱭᱟ",
        "proxyblocker": "ᱯᱨᱚᱠᱥᱤ ᱮᱥᱮᱫᱤᱡ",
        "movepagebtn": "ᱥᱟᱦᱴᱟ ᱥᱟᱦᱟᱭᱢᱮ",
-       "pagemovedsub": "Ocogoḱ do hoena",
+       "pagemovedsub": "ᱚᱪᱟᱜ ᱫᱚ ᱦᱩᱭᱱᱟ",
        "movelogpage": "ᱛᱟᱞᱟ ᱪᱟᱹᱵᱤ ᱚᱪᱚᱜᱽᱢᱮ",
        "revertmove": "ᱨᱩᱣᱟᱹᱲ ᱟᱹᱜᱩ",
        "export": "ᱟᱹᱜᱩᱭᱮᱱ ᱥᱟᱦᱴᱟᱠᱚ",
        "fileduplicatesearch": "ᱥᱮᱸᱫᱽᱨᱟᱭ ᱢᱮ ᱵᱟᱹᱲᱤᱡ ᱨᱮᱫᱠᱚ",
        "fileduplicatesearch-submit": "ᱥᱮᱸᱫᱽᱨᱟ",
        "specialpages": "ᱵᱤᱥᱮᱥ ᱥᱟᱦᱴᱟᱠᱚ",
-       "external_image_whitelist": "#Noa sakam do cet leka menaḱa oṅkage dohoemẽ\n#Sanam okte re jạhiren kuṭrạ latar re (khạli hạtiń //talare) bạisạomẽ\n#Noako do bahre reaḱ (hotlinked) chubi reaḱ URL saõte milạo hoyoḱa\n#Okako milạḱa, onako do chubi lekate udugoḱa, baṅkhan do khali chubi joṛao udugoḱa\n#Noa layen reaḱ ehoṕre # menaḱa ona layenko menko hisapte beohar hoyoḱka\n#Noa do kas-baṅ rimjhạoaḱge\n#Noa dag cetanre regex kuṭrạ bạsạomẽ. Noa layen cetleka menaḱa oṅkage dohoemẽ</pre>",
+       "external_image_whitelist": "#ᱱᱚᱣᱟ ᱥᱟᱦᱴᱟ ᱫᱚ ᱪᱮᱛ ᱞᱮᱠᱟ ᱢᱮᱱᱟᱜ-ᱟ ᱚᱝᱠᱟᱜᱮ ᱫᱚᱦᱚᱭᱢᱮ\n#ᱡᱚᱛᱚ ᱚᱠᱛᱚ ᱨᱮ ᱡᱟᱹᱦᱤᱨᱮᱱ ᱠᱩᱴᱨᱟᱹ ᱞᱟᱛᱟᱨ ᱨᱮ (ᱠᱷᱟᱹᱞᱤ ᱦᱟᱹᱴᱤᱧ //ᱛᱟᱞᱟᱨᱮ) ᱵᱟᱹᱭᱥᱟᱹᱣᱢᱮ\n#ᱱᱚᱣᱟ ᱠᱚ ᱫᱚ ᱵᱟᱨᱦᱮ ᱨᱮᱭᱟᱜ (hotlinked) ᱪᱤᱛᱟᱹᱨ ᱨᱮᱭᱟᱜ URL ᱥᱟᱶᱛᱮ ᱢᱤᱞᱟᱹᱣ ᱦᱩᱭᱩᱜ-ᱟ\n#ᱚᱠᱟᱠᱩ ᱢᱤᱞᱟᱹᱜ-ᱟ, ᱚᱱᱟᱠᱩ ᱫᱚ ᱪᱤᱛᱟᱹᱨ ᱞᱮᱠᱟᱛᱮ ᱩᱫᱩᱜᱚᱜ-ᱟ, ᱵᱟᱝᱠᱷᱟᱱ ᱫᱚ ᱠᱷᱟᱹᱞᱤ ᱪᱤᱛᱟᱨ ᱡᱚᱱᱚᱲ ᱩᱫᱩᱜᱚᱜ-ᱟ\n#ᱱᱚᱣᱟ ᱞᱟᱭᱤᱱ ᱨᱮᱭᱟᱜ ᱮᱛᱦᱚᱵᱨᱮ # ᱢᱮᱱᱟᱜ-ᱟ ᱚᱱᱟ ᱞᱟᱭᱤᱱᱠᱚ ᱢᱮᱱᱠᱚ ᱦᱤᱥᱟᱹᱵᱛᱮ ᱵᱮᱵᱦᱟᱨ ᱦᱩᱭᱩᱜ-ᱟ\n#ᱱᱚᱣᱟ ᱫᱚ ᱨᱤᱢᱡᱷᱟᱹᱣᱜᱮ\n#ᱱᱚᱣᱟ ᱫᱟᱜᱽ ᱪᱮᱛᱟᱱᱨᱮ regex ᱠᱩᱴᱨᱟᱹ ᱵᱟᱹᱭᱥᱟᱹᱣᱢᱮ᱾ ᱱᱚᱣᱟ ᱞᱟᱭᱤᱱ ᱪᱮᱫᱞᱮᱠᱟ ᱢᱮᱱᱟᱜ-ᱟ ᱚᱝᱠᱟᱜᱮ ᱫᱚᱦᱚᱭᱢᱮ</pre>",
        "tag-filter": "[[Special:Tags|Tag]] ᱪᱷᱟᱹᱠᱱᱤ:",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1|ᱥᱟᱛᱚᱢ|ᱥᱟᱛᱚᱢᱠᱩ}}]]: $2)",
        "tags-active-yes": "ᱦᱮᱸ",
index d2c3b55..02fa5be 100644 (file)
        "userjspreview": "'''Arricorda ca stai sulu tistanno/vidennu 'n antiprima lu tò javascript pirsunali, nun hà statu ancora sarvatu!'''",
        "sitecsspreview": "<strong>Arricòrdati chi chista è sulu n'antiprima di stu CSS. Ancora nun fu sarvatu!</strong>",
        "sitejspreview": "<strong>Arricòrdati chi chista è sulu n'antiprima di stu còdici JavaScript. Ancora nun fu sarvatu!</strong>",
-       "userinvalidcssjstitle": "'''Accura:''' Nun esisti arcuna skin cu nomu \"$1\". S'arricorda ca li pàggini pi li .css e .js pirsunalizzati hannu la nizziali dû tìtulu minùscula, p'asempiu {{ns:user}}:Asempiu/vector.js e nun {{ns:user}}:Asempiu/Vector.css.",
+       "userinvalidconfigtitle": "'''Accura:''' Nun esisti arcuna skin cu nomu \"$1\". S'arricorda ca li pàggini pi li .css e .js pirsunalizzati hannu la nizziali dû tìtulu minùscula, p'asempiu {{ns:user}}:Asempiu/vector.js e nun {{ns:user}}:Asempiu/Vector.css.",
        "updated": "(Aggiurnatu)",
        "note": "<strong>Nota:</strong>",
        "previewnote": "<strong>Arricòrdati ca chista è sulu n'antiprima.</strong>\nLi tò canciamenti ancora nun foru sarvati!",
        "prefs-files": "File",
        "prefs-custom-css": "CSS pirsunalizzatu",
        "prefs-custom-js": "JavaScript pirsunalizzatu",
-       "prefs-common-css-js": "CSS/JavaScript cunnivisu tra tutti li peddi:",
+       "prefs-common-config": "CSS/JavaScript cunnivisu tra tutti li peddi:",
        "prefs-reset-intro": "Poi adupirari sta pàggina p'azzirari li tò prifirenzi a chiddi pridifinuti dû situ.\nSt'opirazzioni nun si pò annullari doppu ch'è fatta.",
        "prefs-emailconfirm-label": "Cunvàlida dâ posta elittrònica:",
        "youremail": "Nnirizzu di posta elittrònica:",
index c58040b..cd442a3 100644 (file)
        "userjspreview": "'''Mynd that ye're juist testin/previewin yer uiser JavaScript; it haesna been hained yet!'''",
        "sitecsspreview": "<strong>Mynd that ye'r yinly previewing this CSS.\nIt's no been hained yet!</strong>",
        "sitejspreview": "<strong>Mynd that ye'r yinly previewing this JavaScript code.\nIt's no been hained yet!</strong>",
-       "userinvalidcssjstitle": "<strong>Warnishmant</strong> Thaur's na ae skin \"$1\". Mynd that yer ain .css n .js pages uise ae lowercase teetle, e.g. {{ns:user}}:Foo/vector.css in steid o {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Warnishmant</strong> Thaur's na ae skin \"$1\". Mynd that yer ain .css n .js pages uise ae lowercase teetle, e.g. {{ns:user}}:Foo/vector.css in steid o {{ns:user}}:Foo/Vector.css.",
        "updated": "(Updatit)",
        "note": "'''Mynd:'''",
        "previewnote": "<strong>Mynd that this is yinlie ae luikower.</strong>\nYer chynges hae na been hained yet!",
        "prefs-files": "Files",
        "prefs-custom-css": "Custom CSS",
        "prefs-custom-js": "Custom JS",
-       "prefs-common-css-js": "Shaired CSS/JavaScript fer aw skins:",
+       "prefs-common-config": "Shaired CSS/JavaScript fer aw skins:",
        "prefs-reset-intro": "Ye can uise this page tae reset yer preeferances til the steid defauts.\nThis canna be ondun.",
        "prefs-emailconfirm-label": "Wab-mail confirmation:",
        "youremail": "Yer email:",
index e23d82d..6b0dc67 100644 (file)
        "mytalk": "بحث",
        "anontalk": "بحث",
        "navigation": "رھنمائي",
-       "and": "&#32؛۽",
+       "and": "&#32;۽",
        "faq": "ڪپس",
        "actions": "ڪارگذاريون",
        "namespaces": "نانءُپولارَ",
index 93a1a40..06de033 100644 (file)
        "userjsyoucanpreview": "'''Suggerimentu:''' Usa lu buttoni '''Visuarizza antiprimma''' pa prubà li nobi JS primma di sàivvaddi.",
        "usercsspreview": "'''Ammitanti ch'è soru un'antiprimma di lu propriu CSS passunari; li mudìfigghi nò so ancora isthaddi sàivvaddi!'''",
        "userjspreview": "'''Ammitanti ch'è soru un'antiprimma pa prubà lu propriu JavaScript passunari; li mudìfigghi nò so ancora isthaddi sàivvaddi!'''",
-       "userinvalidcssjstitle": "'''Attinzioni:''' Nò isisthi nisciun aipettu gràficu \"$1\". Amminta chi li pàgini pa li .css e .js passunari àni lu primu caràtteri di lu tìturu minori, cumenti {{ns:user}}:Foo/vector.css e nò {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Attinzioni:''' Nò isisthi nisciun aipettu gràficu \"$1\". Amminta chi li pàgini pa li .css e .js passunari àni lu primu caràtteri di lu tìturu minori, cumenti {{ns:user}}:Foo/vector.css e nò {{ns:user}}:Foo/Vector.css.",
        "updated": "(Aggiornaddu)",
        "note": "'''NOTA:'''",
        "previewnote": "'''Attinzioni: chistha è soru un'antiprimma. Li mudifigghi a la pagina NÒ so ancora isthaddi saivvaddi!'''",
index 1436537..64faa2e 100644 (file)
        "userjsyoucanpreview": "'''Tip:''' Usadad 'Cohuatlöx cuáxiit' testom me hun JS xuniim 'depre.",
        "usercsspreview": "'''He cuáxiit he CSSde caitom, zo necoccebj xuniim!'''",
        "userjspreview": "'''He cuáxiit/testom he JavaScriptde caitom, zo necoccebj xuniim!'''",
-       "userinvalidcssjstitle": "'''Attencion:''' Skin \"$1\" coccebj ne'dáár. Regardom .css ö .js páhinám usadad titlenam lowercase zo, yanuiíxzo mii yahöxde ti {{ns:user}}:Foo/vector.css opposadadde {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Attencion:''' Skin \"$1\" coccebj ne'dáár. Regardom .css ö .js páhinám usadad titlenam lowercase zo, yanuiíxzo mii yahöxde ti {{ns:user}}:Foo/vector.css opposadadde {{ns:user}}:Foo/Vector.css.",
        "updated": "(varupdatenám)",
        "note": "'''Notificacion:'''",
        "previewnote": "'''Jan coccebj cuáxiit zo; quiíx necoccebj xuniim!'''",
index da31a63..de801f4 100644 (file)
        "userjspreview": "<strong>Honga kaŋ war goo ma šii/moofur de war JavaScript goykaa ga.\nA mana gaabundi jina!</strong>",
        "sitecsspreview": "<strong>Honga kaŋ war mma moofur de CSS woo ga.\n A mana gaabundi jina!</strong>",
        "sitejspreview": "<strong>Honga kaŋ war mma moofur de JavaScript ašariyaa woo ga.\nA mana gaabundi jina!</strong>",
-       "userinvalidcssjstitle": "<strong>Yaamar:</strong> \"$1\" kuuru kul šii.\nLaada .css nda .js moɲey ga goy nda karfu kayna maa, sanda {{ns:user}}:Foo/vector.css manti {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Yaamar:</strong> \"$1\" kuuru kul šii.\nLaada .css nda .js moɲey ga goy nda karfu kayna maa, sanda {{ns:user}}:Foo/vector.css manti {{ns:user}}:Foo/Vector.css.",
        "updated": "(Taagante)",
        "note": "<strong>Laasaabu:</strong>",
        "previewnote": "<strong>Honga kaŋ war moofuryan de ti wo.</strong>\nWar barmawey mana gaabundi jina!",
        "prefs-files": "Tukey",
        "prefs-custom-css": "Laada CSS",
        "prefs-custom-js": "Laada JavaScript",
-       "prefs-common-css-js": "CSS/JavaScript kaŋ kuurey k'a may:",
+       "prefs-common-config": "CSS/JavaScript kaŋ kuurey k'a may:",
        "prefs-reset-intro": "War ga hin ka goy nda moɲoo woo ka war ibaayey yeeti nungoo tilasu alhaaley ga.\nWoo ši hin ka taafeeri.",
        "prefs-emailconfirm-label": "Bataga tabatandiyan:",
        "youremail": "Bataga:",
index 2ebb63a..fe79eb2 100644 (file)
        "userjsyoucanpreview": "'''Patarėms:''' Nauduokat „{{int:showpreview}}“ mīgtoka, kū ėšmiegintomiet sava naujaji JS prīš anou ėšsaugont.",
        "usercsspreview": "'''Napamirškėt, kū Tamsta tėk parveizėt sava nauduotoja CSS, ans da nabova ėšsauguots!'''",
        "userjspreview": "'''Nepamirškėt, kū Tamsta tėk testoujat/parvaizėt sava nauduotoja ''JavaScript'', ans da nabova ėšsauguots!'''",
-       "userinvalidcssjstitle": "'''Diemesė:''' Nė juokės ėšruodos „$1“. Napamirškėt, kū sava .css ėr .js poslapē nauduo pavadėnėma mažuosiomės raidiemis, pvz., Nauduotuos:Foo/vector.css, o ne Nauduotuos:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Diemesė:''' Nė juokės ėšruodos „$1“. Napamirškėt, kū sava .css ėr .js poslapē nauduo pavadėnėma mažuosiomės raidiemis, pvz., Nauduotuos:Foo/vector.css, o ne Nauduotuos:Foo/Vector.css.",
        "updated": "(Atnaujėnta)",
        "note": "'''Žėniuo:'''",
        "previewnote": "'''Napamirškat, kū tas tėktās tier parvaiza, pakeitėmā da nier ėšsauguoti!'''",
        "prefs-files": "Abruozdielē",
        "prefs-custom-css": "Asabėšks CSS",
        "prefs-custom-js": "Asabėšks JavaScript",
-       "prefs-common-css-js": "Bendros CSS/JavaScript vėsuom aplinkuom:",
+       "prefs-common-config": "Bendros CSS/JavaScript vėsuom aplinkuom:",
        "prefs-reset-intro": "Čiuonās galat grōžintė vėsus sava nūstatīmus ont pradėniu (kap būn tiktās prisijongus).\nTas dalīks nab'atšaukiams.",
        "prefs-emailconfirm-label": "Tėkrā tuokis el. paštos?",
        "youremail": "El. paštos:",
index ca5597b..a9f8716 100644 (file)
        "userjspreview": "'''Zapamite da je ovo samo test/pretpregled Vaše JavaScript-e.'''\n'''Još uvijek nije snimljena!'''",
        "sitecsspreview": "'''Zapamtite ovo je samo izgled Vašeg CSS-a.'''\n'''Još uvijek nije snimljen!'''",
        "sitejspreview": "'''Zapamtite ovo je samo izgled ovog koda JavaScripte.'''\n'''Još uvijek nije snimljen!'''",
-       "userinvalidcssjstitle": "'''Upozorenje:''' Nema skina pod imenom \"$1\".\nUpamtite da korisničke .css i .js stranice koriste naslov s malim slovom, npr. {{ns:user}}:Foo/monobook.css umjesto {{ns:user}}:Foo/Monobook.css.",
+       "userinvalidconfigtitle": "'''Upozorenje:''' Nema skina pod imenom \"$1\".\nUpamtite da korisničke .css i .js stranice koriste naslov s malim slovom, npr. {{ns:user}}:Foo/monobook.css umjesto {{ns:user}}:Foo/Monobook.css.",
        "updated": "(Osvježeno)",
        "note": "'''Napomena:'''",
        "previewnote": "<strong>Ne zaboravite da je ovo samo pregled</strong>\nIzmjene stranice nisu još sačuvane!",
        "prefs-files": "Datoteke",
        "prefs-custom-css": "Prilagođeni CSS",
        "prefs-custom-js": "Prilagođeni JS",
-       "prefs-common-css-js": "Zajednički CSS/JS za sve izglede (skinove):",
+       "prefs-common-config": "Zajednički CSS/JS za sve izglede (skinove):",
        "prefs-reset-intro": "Možete koristiti ovu stranicu da poništite Vaše postavke na ovom sajtu na pretpostavljene vrijednosti.\nOvo se ne može vratiti unazad.",
        "prefs-emailconfirm-label": "E-mail potvrda:",
        "youremail": "Vaša e-pošta / Ваша е-пошта*",
index 7849d68..d886354 100644 (file)
        "prefs-files": "ၾၢႆႇ",
        "prefs-custom-css": "CSS ႁင်းတူဝ်",
        "prefs-custom-js": "JavaScript ႁင်းတူဝ်",
-       "prefs-common-css-js": "CSS/Javascript ၸိူဝ်းၽႄႈၸႂ်ႉဝႆႉ တႃႇ ၽိဝ်ၼင်တင်းသဵင်ႈ :",
+       "prefs-common-config": "CSS/Javascript ၸိူဝ်းၽႄႈၸႂ်ႉဝႆႉ တႃႇ ၽိဝ်ၼင်တင်းသဵင်ႈ :",
        "prefs-reset-intro": "ၸဝ်ႈၵဝ်ႇ တေၸၢင်ႈၸႂ်ႉၼႃႈလိၵ်ႈၼႆႉ တွၼ်ႈတႃႇ တင်ႈၶိုၼ်း လွင်ႈလႆႈၸႂ်ၸဝ်ႈၵဝ်ႇ ၸူးၵႃႈတီႈ သၢႆႉ ပိူင်ၵႅဝ်ႈ။ ၼႆႉမၼ်းတေဢမ်ႇၸၢင်ႈ ၶိုၼ်းမႄးၶိုင်ၶိုၼ်း။",
        "prefs-emailconfirm-label": "ၶေႃႈၼႄႉၼွၼ်း ဢီးမေးလ် :",
        "youremail": "ဢီးမေးလ် :",
index 0fb539b..41c87d4 100644 (file)
        "userjspreview": "'''ඔබ සිදුකරන්නේ ඔබගේ පරිශීලක ජාවාස්ක්‍රිප්ට් පරික්ෂා කිරීම/පෙර-දසුන පමණක් බව ධාරණය කරන්න.'''\n'''එය තවමත් සුරැකීමට ලක් කොට නොමැත!'''",
        "sitecsspreview": "'''ඔබ දකින්නේ මෙම CSS හි පෙරදසුනක් පමණක් බව සිහි තබාගන්න.'''\n'''එය තවමත් සුරැකීමට ලක් කොට නොමැත!'''",
        "sitejspreview": "'''ඔබ දකින්නේ මෙම JavaScript කේතයෙහි පෙරදසුනක් පමණක් බව සිහි තබාගන්න.'''\n'''එය තවමත් සුරැකීමට ලක් කොට නොමැත!'''",
-       "userinvalidcssjstitle": "'''අවවාදයයි:''' ඡවියක් නොමැත \"$1\".\nරීති ප්‍රකාරව .css හා .js පිටු විසින් ඉංග්‍රීසි කුඩා-අකුරු ශීර්ෂ භාවිතා කෙරෙන බව සිහි තබා ගන්න, නිදසුන. {{ns:user}}:Foo/vector.css මිස {{ns:user}}:Foo/Vector.css නොවන බව.",
+       "userinvalidconfigtitle": "'''අවවාදයයි:''' ඡවියක් නොමැත \"$1\".\nරීති ප්‍රකාරව .css හා .js පිටු විසින් ඉංග්‍රීසි කුඩා-අකුරු ශීර්ෂ භාවිතා කෙරෙන බව සිහි තබා ගන්න, නිදසුන. {{ns:user}}:Foo/vector.css මිස {{ns:user}}:Foo/Vector.css නොවන බව.",
        "updated": "(යාවත්කාලීන)",
        "note": "'''සටහන:'''",
        "previewnote": "'''මෙය පෙරදසුනක් පමණක් බව සිහිතබාගන්න.'''\nඔබගේ වෙනස්කිරීම් තවමත් සුරැකීමට ලක් කොට නොමැත!",
        "prefs-files": "ගොනු",
        "prefs-custom-css": "අභිරුචි CSS",
        "prefs-custom-js": " අභිරුචි JS",
-       "prefs-common-css-js": "සියළු සිවි සඳහා හවුලේ භාවිත  CSS/ජාවා ස්ක්‍රිප්ට්:",
+       "prefs-common-config": "සියළු සිවි සඳහා හවුලේ භාවිත  CSS/ජාවා ස්ක්‍රිප්ට්:",
        "prefs-reset-intro": "ඔබගේ අභිප්‍රේතයන්, අඩවි පෙරනිමි වෙතට යළි-පිහිටුවීම සඳහා, ඔබ හට මෙම පිටුව භාවිතා කල හැක.\nමෙය අහෝසි කල නොහැක.",
        "prefs-emailconfirm-label": "විද්‍යුත්-ලිපිනය තහවුරුකිරීම:",
        "youremail": "විද්‍යුත් තැපෑල:",
index e962b50..be54581 100644 (file)
        "userjspreview": "'''Nezabudnite, že iba testujete/náhľad vášho používateľského JavaScriptu, ešte nebol uložený!'''",
        "sitecsspreview": "'''Nezabudnite, že toto je iba náhľad tohto CSS.'''\n'''Zatiaľ nebolo uložené!'''",
        "sitejspreview": "'''Nezabudnite, že toto je iba náhľad tohto JavaScriptu.'''\n'''Zatiaľ nebol uložený!'''",
-       "userinvalidcssjstitle": "'''Upozornenie:''' Neexistuje vzhľad „$1“. Pamätajte, že vlastné .css a .js stránky používajú názov s malými písmenami, napr. {{ns:user}}:Foo/vector.css a nie {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Upozornenie:''' Neexistuje vzhľad „$1“. Pamätajte, že vlastné .css a .js stránky používajú názov s malými písmenami, napr. {{ns:user}}:Foo/vector.css a nie {{ns:user}}:Foo/Vector.css.",
        "updated": "(Aktualizovaný)",
        "note": "'''Poznámka: '''",
        "previewnote": "'''Nezabudnite, toto je iba náhľad stránky, ktorú upravujete.\nZmeny ešte nie sú uložené!'''",
        "prefs-files": "Súbory",
        "prefs-custom-css": "Vlastný CSS",
        "prefs-custom-js": "Vlastný JS",
-       "prefs-common-css-js": "Spoločné CSS/JS pre všetky témy:",
+       "prefs-common-config": "Spoločné CSS/JS pre všetky témy:",
        "prefs-reset-intro": "Túto stránku môžete použiť na vrátenie predvolených hodnôt vašich nastavení.\nTúto operáciu nemožno vrátiť.",
        "prefs-emailconfirm-label": "Overenie e-mailu:",
        "youremail": "Váš e-mail²",
index b08b5cf..5c7b60f 100644 (file)
        "userjspreview": "'''Ne pozabite, da svoj uporabniški JavaScript le preizkušate/predogledujete.'''\n'''Ni še bil shranjen!'''",
        "sitecsspreview": "'''Ne pozabite, da ta CSS samo preizkušate.'''\n'''Ni še bil shranjen!'''",
        "sitejspreview": "'''Ne pozabite, da kodo tega JavaScripta samo preizkušate.'''\n'''Ni še bila shranjena!'''",
-       "userinvalidcssjstitle": "'''Opozorilo:''' Koža »$1« ne obstaja.\nVedite, da .css in .js strani po meri uporabljajo naslov z malo začetnico, npr. {{ns:user}}:Blabla/vector.css namesto {{ns:user}}:Blabla/Vector.css.",
+       "userinvalidconfigtitle": "'''Opozorilo:''' Koža »$1« ne obstaja.\nVedite, da .css in .js strani po meri uporabljajo naslov z malo začetnico, npr. {{ns:user}}:Blabla/vector.css namesto {{ns:user}}:Blabla/Vector.css.",
        "updated": "(Posodobljeno)",
        "note": "'''Opomba:'''",
        "previewnote": "'''Vedite, da stran le predogledujete.'''\nVaših sprememb še nismo shranili!",
        "prefs-files": "Datoteke",
        "prefs-custom-css": "CSS po meri",
        "prefs-custom-js": "JS po meri",
-       "prefs-common-css-js": "Skupni CSS/JS za vse kože:",
+       "prefs-common-config": "Skupni CSS/JS za vse kože:",
        "prefs-reset-intro": "To stran lahko uporabite za ponastavitev nastavitev na privzete za to spletišče.\nTega ni mogoče razveljaviti.",
        "prefs-emailconfirm-label": "Potrditev e-pošte:",
        "youremail": "E-poštni naslov:",
        "thumbnail_dest_directory": "Ne morem ustvariti ciljnega direktorija",
        "thumbnail_image-type": "Vrsta slike ni podprta",
        "thumbnail_gd-library": "Nepopolna konfiguracija knjižice GD: manjka funkcija $1",
+       "thumbnail_image-size-zero": "Zdi se, da je velikost datoteke slike enaka nič.",
        "thumbnail_image-missing": "Kaže, da datoteka manjka: $1",
        "thumbnail_image-failure-limit": "Nedavno je bilo preveč spodletelih poskusov ($1 ali več) izdelave sličice. Prosimo, poskusite znova pozneje.",
        "import": "Uvoz strani",
index 835e324..855fb7a 100644 (file)
        "userjsyoucanpreview": "'''Tipp:''' Benutze dann Vurschau-Button, im dei neues JS vur damm Speichern zu testa.",
        "usercsspreview": "== Vurschau Dennes Nutzer-CSS ==\n'''Beachte:''' Noach damm Speichern mußt du dennen Browser oaweisa, de neue Version zu loada: '''Mozilla/Firefox:''' ''Strg-Shift-R'', '''Internet Explorer:''' ''Strg-F5'', '''Opera:''' ''F5'', '''Safari:''' ''Cmd-Shift-R'', '''Konqueror:''' ''F5''.",
        "userjspreview": "== Vurschau Dennes Nutzer-JavaScript ==\n'''Beachte:''' Noach damm Speichern mußt du dennen Browser oaweisa, de neue Version zu loada: '''Mozilla/Firefox:''' ''Strg-Shift-R'', '''Internet Explorer:''' ''Strg-F5'', '''Opera:''' ''F5'', '''Safari:''' ''Cmd-Shift-R'', '''Konqueror:''' ''F5''.",
-       "userinvalidcssjstitle": "'''OCHTICHE:''' Skin „$1“ existiert ne. Bedenke, doß nutzerspezifische .css- und .js-Seyta miet a'm Kleenbuchstaba oafanga missa, olso beispielsweise ''{{ns:user}}:Mustermann/vector.css'' oa Stalle vu ''{{ns:user}}:Mustermoan/Vector.css''.",
+       "userinvalidconfigtitle": "'''OCHTICHE:''' Skin „$1“ existiert ne. Bedenke, doß nutzerspezifische .css- und .js-Seyta miet a'm Kleenbuchstaba oafanga missa, olso beispielsweise ''{{ns:user}}:Mustermann/vector.css'' oa Stalle vu ''{{ns:user}}:Mustermoan/Vector.css''.",
        "previewnote": "'''Dies ies ock anne Vurschau, de Seite wurde noo nee gespeichert!'''",
        "previewconflict": "Diese Vurschau gitt 'n Inhalt des obern Textfeldes wieder. Su werd de Seite aussahn, wenn du jitz speicherst.",
        "session_fail_preview": "'''Denne Beoarbeetung konnte ne gespeichert waan, do Sitzungsdaten verlorn geganga sein.\nBitte versiche is erneut, indem du under dar fulgenda Textvurschau noo amols uff „Seyte speichern“ klickst.\nSullte doas Problem bestiehn bleiba, [[Special:UserLogout|melde diech ob]] und danone wieder oa.'''",
index a679ae4..c148b64 100644 (file)
        "userjspreview": "'''Vini re se kjo është vetëm një provë ose parapamje e faqes tuaj JavaScript, ajo nuk është ruajtur akoma!'''",
        "sitecsspreview": "<strong>Vini re! Ju jeni duke inspektuar CSS-në!\nNuk është ruajtur ende!</strong>",
        "sitejspreview": "<strong>Vini re! Ju jeni duke inspektuar këtë kod JavaScript. \nNuk është ruajtur ende!</strong>",
-       "userinvalidcssjstitle": "'''Kujdes:''' Nuk ka pamje të quajtur \"$1\". Vini re se faqet .css dhe .js përdorin titull me gërma të vogla, p.sh. {{ns:user}}:Foo/vector.css, jo {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Kujdes:''' Nuk ka pamje të quajtur \"$1\". Vini re se faqet .css dhe .js përdorin titull me gërma të vogla, p.sh. {{ns:user}}:Foo/vector.css, jo {{ns:user}}:Foo/Vector.css.",
        "updated": "(E ndryshuar)",
        "note": "'''Shënim:'''",
        "previewnote": "'''Mos harro që kjo është vetëm një parapamje.'''\nNdryshimet e tua nuk janë ruajtur ende!",
        "prefs-files": "Figura",
        "prefs-custom-css": "CSS i përpunuem",
        "prefs-custom-js": "JavaScripti i përpunuar",
-       "prefs-common-css-js": "CSS/Javascript të përbashkët për të gjitha pamjet:",
+       "prefs-common-config": "CSS/Javascript të përbashkët për të gjitha pamjet:",
        "prefs-reset-intro": "Mundeni me përdorë këtë faqe për me i kthy parapëlqimet tueja në ato të paracaktuemet e faqes.\nKjo nuk mundet me u zhbâ.",
        "prefs-emailconfirm-label": "Konfirmimi i emailit:",
        "youremail": "Adresa e email-it*",
index 12a90f7..38e81e5 100644 (file)
        "aboutpage": "Project:О нама",
        "copyright": "Садржај је доступан под лиценцом $1 осим ако је другачије наведено.",
        "copyrightpage": "{{ns:project}}:Ауторска права",
-       "currentevents": "Ð\9dовости",
+       "currentevents": "Ð\90кÑ\82Ñ\83елности",
        "currentevents-url": "Project:Новости",
        "disclaimers": "Одрицање одговорности",
        "disclaimerpage": "Project:Одрицање одговорности",
        "edithelp": "Помоћ при уређивању",
        "helppage-top-gethelp": "Помоћ",
-       "mainpage": "Ð\93лавна Ñ\81Ñ\82Ñ\80аниÑ\86а",
-       "mainpage-description": "Ð\93лавна Ñ\81Ñ\82Ñ\80аниÑ\86а",
+       "mainpage": "Главна страна",
+       "mainpage-description": "Главна страна",
        "policy-url": "Project:Правила",
        "portal": "Портал заједнице",
        "portal-url": "Project:Портал заједнице",
        "nstab-template": "Шаблон",
        "nstab-help": "Помоћ",
        "nstab-category": "Категорија",
-       "mainpage-nstab": "Ð\93лавна Ñ\81Ñ\82Ñ\80аниÑ\86а",
+       "mainpage-nstab": "Главна страна",
        "nosuchaction": "Нема такве радње",
        "nosuchactiontext": "Радња наведена у адреси није исправна.\nМожда сте погрешно написали адресу или сте пратили застарелу везу.\nМогуће је и да се ради о грешци у софтверу викија.",
        "nosuchspecialpage": "Нема такве посебне странице",
        "userjspreview": "<strong>Ово је само преглед јаваскрипта.\nСтраница још није сачувана!</strong>",
        "sitecsspreview": "<strong>Ово је само преглед CSS-а.\nСтраница још није сачувана!</strong>",
        "sitejspreview": "<strong>Ово је само преглед јаваскрипта.\nСтраница још није сачувана!</strong>",
-       "userinvalidcssjstitle": "<strong>Упозорење:</strong> не постоји тема „$1“.\nПрилагођене странице CSS и јаваскрипт почињу малим словом, нпр. {{ns:user}}:Foo/vector.css, а не {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Упозорење:</strong> не постоји тема „$1“.\nПрилагођене странице CSS и јаваскрипт почињу малим словом, нпр. {{ns:user}}:Foo/vector.css, а не {{ns:user}}:Foo/Vector.css.",
        "updated": "(ажурирано)",
        "note": "<strong>Напомена:</strong>",
        "previewnote": "<strong>Не заборавите да је ово само претпреглед.</strong>\nВаше измене још нису сачуване!",
        "powersearch-toggleall": "Све",
        "powersearch-togglenone": "Ништа",
        "powersearch-remember": "Запамти мој избор за будуће претраге",
-       "search-external": "СпоÑ\99на претрага",
+       "search-external": "СпоÑ\99аÑ\88Ñ\9aа претрага",
        "searchdisabled": "Претрага је онемогућена.\nУ међувремену можете тражити преко Гугла.\nУпамтите да његови пописи овог викија могу бити застарели.",
        "search-error": "Дошло је до грешке приликом претраге: $1",
        "search-warning": "Упозорење приликом претраге: $1",
        "prefs-files": "Датотеке",
        "prefs-custom-css": "Прилагођени CSS",
        "prefs-custom-js": "Прилагођени јаваскрипт",
-       "prefs-common-css-js": "Дељени CSS/Јаваскрипт за све теме:",
+       "prefs-common-config": "Дељени CSS/Јаваскрипт за све теме:",
        "prefs-reset-intro": "Можете користити ову страницу да поништите своја подешавања на подразумеване вредности.\nОва радња се не може вратити.",
        "prefs-emailconfirm-label": "Потврда имејла:",
        "youremail": "Имејл:",
        "deletedcontributions": "Обрисани кориснички доприноси",
        "deletedcontributions-title": "Обрисани кориснички доприноси",
        "sp-deletedcontributions-contribs": "доприноси",
-       "linksearch": "Ð\9fÑ\80еÑ\82Ñ\80ага Ñ\81поÑ\99ниÑ\85 Ð²ÐµÐ·Ð°",
+       "linksearch": "Ð\9fÑ\80еÑ\82Ñ\80ажи Ñ\81поÑ\99аÑ\88Ñ\9aе Ð²ÐµÐ·Ðµ",
        "linksearch-pat": "Образац претраге:",
        "linksearch-ns": "Именски простор:",
        "linksearch-ok": "Претражи",
        "redirect-file": "Назив датотеке",
        "redirect-logid": "ID дневника",
        "redirect-not-exists": "Вредност није пронађена",
-       "fileduplicatesearch": "Ð\9fÑ\80еÑ\82Ñ\80ага Ð´Ñ\83пликаÑ\82а",
+       "fileduplicatesearch": "Ð\9fÑ\80еÑ\82Ñ\80ажи Ð´Ñ\83пликаÑ\82е",
        "fileduplicatesearch-summary": "Претрага дуплираних датотека према хеш вредности.",
        "fileduplicatesearch-filename": "Назив датотеке:",
        "fileduplicatesearch-submit": "Претражи",
index fca6d7c..c35d8ba 100644 (file)
        "userjspreview": "'''Ovo je samo pregled javaskripta.'''\n'''Stranica još nije sačuvana!'''",
        "sitecsspreview": "'''Ovo je samo pregled CSS-a.'''\n'''Stranica još nije sačuvana!'''",
        "sitejspreview": "'''Ovo je samo pregled javaskripta.'''\n'''Stranica još nije sačuvana!'''",
-       "userinvalidcssjstitle": "<strong>Upozorenje:</strong> ne postoji tema „$1“.\nPrilagođene stranice CSS i javaskript počinju malim slovom, npr. {{ns:user}}:Foo/vector.css, a ne {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Upozorenje:</strong> ne postoji tema „$1“.\nPrilagođene stranice CSS i javaskript počinju malim slovom, npr. {{ns:user}}:Foo/vector.css, a ne {{ns:user}}:Foo/Vector.css.",
        "updated": "(Ažurirano)",
        "note": "<strong>Napomena:</strong>",
        "previewnote": "<strong>Ne zaboravite da je ovo samo pretpregled.</strong>\nVaše izmene još nisu sačuvane!",
        "prefs-files": "Datoteke",
        "prefs-custom-css": "Prilagođeni CSS",
        "prefs-custom-js": "Prilagođeni javaskript",
-       "prefs-common-css-js": "Deljeni CSS/javaskript za sve teme:",
+       "prefs-common-config": "Deljeni CSS/javaskript za sve teme:",
        "prefs-reset-intro": "Možete koristiti ovu stranicu da poništite svoje postavke na podrazumevane vrednosti.\nOva radnja se ne može vratiti.",
        "prefs-emailconfirm-label": "Potvrda imejla:",
        "youremail": "Imejl:",
index 148cde9..cbf9593 100644 (file)
        "userjspreview": "== Foarskau fon dien Benutser-CSS ==\n'''Beoachtje:''' Ätter dät Spiekerjen moast du dien Browser kweede, ju näie Version tou leeden: '''Mozilla/Firefox:''' ''Strg-Shift-R'', '''Internet Explorer:''' ''Strg-F5'', '''Opera:''' ''F5'', '''Safari:''' ''Cmd-Shift-R'', '''Konqueror:''' ''F5''.",
        "sitecsspreview": "\"Beoachtje, dät du bloot ne Foarskau fon dit CSS bekiekest.\"\n\"Der wuud noch nit spiekerd!\"",
        "sitejspreview": "'''Beoachtje, dät du bloot n Foarbekiek fon dit JavaScript bekiekest.'''\n'''Dät is noch nit spiekerd!'''",
-       "userinvalidcssjstitle": "'''Woarskauenge:''' Deer existiert neen Skin \"$1\". Betoank jädden, dät benutserspezifiske .css- un .js-Sieden män n Littek-Bouksteeuwe anfange mouten, also t.B. ''{{ns:user}}:Mustermann/vector.css'', nit ''{{ns:user}}:Mustermann/Vector.css''.",
+       "userinvalidconfigtitle": "'''Woarskauenge:''' Deer existiert neen Skin \"$1\". Betoank jädden, dät benutserspezifiske .css- un .js-Sieden män n Littek-Bouksteeuwe anfange mouten, also t.B. ''{{ns:user}}:Mustermann/vector.css'', nit ''{{ns:user}}:Mustermann/Vector.css''.",
        "updated": "(Annerd)",
        "note": "'''Waiwiesenge:'''",
        "previewnote": "'''Dit is man ne Foarbekiek, ju Siede wuude noch nit spiekerd!'''",
        "prefs-files": "Doatäie",
        "prefs-custom-css": "Benutserdefinierde CSS",
        "prefs-custom-js": "Benutserdefinierd JS",
-       "prefs-common-css-js": "Gemeensoam CSS/JS foar aal Skins:",
+       "prefs-common-config": "Gemeensoam CSS/JS foar aal Skins:",
        "prefs-reset-intro": "Du koast disse Siede bruuke, uum do Ienstaalengen ap do Standoarde touräächtousätten.\nDät kon nit moor tourääch troald wäide.",
        "prefs-emailconfirm-label": "E-Mail-Bestäätigenge:",
        "youremail": "E-Mail-Adrässe:",
index fa6ae89..736a629 100644 (file)
        "userjspreview": "'''Inget yén anjeun ukur nguji/nyawang ''javascript'' pamaké anjeun, can disimpen!'''",
        "sitecsspreview": "'''Inget yén ieu CSS ukur pramidang.'''\n'''Can disimpen!'''",
        "sitejspreview": "'''Inget yén ieu kodeu JavaScript ukur pramidang.'''\n'''Can disimpen!'''",
-       "userinvalidcssjstitle": "'''Awas''': kulit \"$1\" mah teu aya. Sing émut yén kaca .css jeung .js mah migunakeun aksara leutik dina judulna, contona baé {{ns:user}}:Foo/vector.css lawan {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Awas''': kulit \"$1\" mah teu aya. Sing émut yén kaca .css jeung .js mah migunakeun aksara leutik dina judulna, contona baé {{ns:user}}:Foo/vector.css lawan {{ns:user}}:Foo/Vector.css.",
        "updated": "(Geus diropéa)",
        "note": "'''Catetan:'''",
        "previewnote": "'''Inget yén ieu ukur pratayang, can disimpen.'''\nÉditan anjeun can disimpen!",
        "prefs-files": "Berkas",
        "prefs-custom-css": "CSS sakahayang",
        "prefs-custom-js": "JavaScript sakahayang",
-       "prefs-common-css-js": "CSS/JavaScript dipaké pikeun sakabéh kulit:",
+       "prefs-common-config": "CSS/JavaScript dipaké pikeun sakabéh kulit:",
        "prefs-reset-intro": "Anjeun bisa maké ieu kaca pikeun mulangkeun préferénsi anjeun ka nu baku.\nMun geus anggeus teu bisa dibolaykeun.",
        "prefs-emailconfirm-label": "Konfirmasi surélék:",
        "youremail": "Surélék:",
        "logentry-protect-unprotect": "$1 {{GENDER:$2|mupus}} panangtayungan ti $3",
        "logentry-protect-protect": "$1 {{GENDER:$2|ditangtayungan}} $3 $4",
        "logentry-upload-upload": "$1 {{GENDER:$2|ngamuat}} $3",
-       "logentry-upload-overwrite": "$1 {{GENDER:$2|ngunggah}} $3 vérsi anyar",
+       "logentry-upload-overwrite": "$1 {{GENDER:$2|ngunjal}} $3 vérsi anyar",
        "logentry-upload-revert": "$1 {{GENDER:$2|diunjal}} $3",
        "log-name-managetags": "Log pangokolaan tag",
        "logentry-managetags-create": "$1 {{GENDER:$2|nyieun}} tag \"$4\"",
index 3ab3adb..fab5c8e 100644 (file)
        "userjspreview": "'''Kom ihåg att du bara testar/förhandsgranskar ditt JavaScript.'''\n'''Det har inte sparats än!'''",
        "sitecsspreview": "'''Kom ihåg att du bara förhandsgranskar detta CSS.''' \n'''Det har ännu inte sparats!'''",
        "sitejspreview": "'''Kom ihåg att du bara förhandsgranskar denna JavaScript-kod.'''\n'''Det har ännu inte sparats!'''",
-       "userinvalidcssjstitle": "'''Varning:''' Utseendet \"$1\" finns inte. Kom ihåg att .css- och .js-sidor för enskilda användare börjar på liten bokstav. Exempel: {{ns:user}}:Foo/vector.css i stället för {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Varning:''' Utseendet \"$1\" finns inte. Kom ihåg att .css- och .js-sidor för enskilda användare börjar på liten bokstav. Exempel: {{ns:user}}:Foo/vector.css i stället för {{ns:user}}:Foo/Vector.css.",
        "updated": "(Uppdaterad)",
        "note": "'''Obs!'''",
        "previewnote": "'''Kom ihåg att detta bara är en förhandsvisning.'''\nDina ändringar har ännu inte sparats!",
        "prefs-files": "Filer",
        "prefs-custom-css": "Personlig CSS",
        "prefs-custom-js": "Personligt JavaScript",
-       "prefs-common-css-js": "Delad CSS/JS för alla utseenden:",
+       "prefs-common-config": "Delad CSS/JS för alla utseenden:",
        "prefs-reset-intro": "Du kan använda den här sidan till att återställa dina inställningar till webbplatsens standardinställningar.\nDetta kan inte återställas.",
        "prefs-emailconfirm-label": "E-postbekräftelse:",
        "youremail": "E-post:",
        "thumbnail_dest_directory": "Kan inte skapa målkatalogen",
        "thumbnail_image-type": "Bildtypen stöds inte",
        "thumbnail_gd-library": "Inkomplett GD library konfigurering: saknar funktionen $1",
+       "thumbnail_image-size-zero": "Bildens filstorlek verkar vara noll.",
        "thumbnail_image-missing": "Fil verkar saknas: $1",
        "thumbnail_image-failure-limit": "Det har nyligen förekommit alltför många misslyckade försök ($1 eller fler) att skapa den här miniatyrbilden. Försök igen senare.",
        "import": "Importera sidor",
index 219a4f6..62318b8 100644 (file)
        "userjspreview": "'''Kumbuka kwamba unajaribu/kuhakiki mandhari ya ukurasa wako wa JavaScript tu.'''\n'''Haijahifadhiwa bado!'''",
        "sitecsspreview": "'''Kumbuka kwamba unahakiki tu mandhari ya CSS hii.'''\n'''Haijahifadhiwa bado!'''",
        "sitejspreview": "'''Kumbuka kwamba unahakiki tu mandhari ya JavaScript hii.'''\n'''Haijahifadhiwa bado!'''",
-       "userinvalidcssjstitle": "'''Onyo:''' Hakuna umbo \"$1\".\nKumbuka kwamba desturi ya kurasa za .css na .js hutumia herufi ndogo, yaani, {{ns:user}}:Foo/vector.css na si {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Onyo:''' Hakuna umbo \"$1\".\nKumbuka kwamba desturi ya kurasa za .css na .js hutumia herufi ndogo, yaani, {{ns:user}}:Foo/vector.css na si {{ns:user}}:Foo/Vector.css.",
        "updated": "(Imesasishwa)",
        "note": "'''Taarifa:'''",
        "previewnote": "'''Hii ni hakikisho tu.''' \nMabadiliko hayajahifadhiwa bado!",
        "prefs-files": "Mafaili",
        "prefs-custom-css": "CSS niliyotunga mwenyewe",
        "prefs-custom-js": "JS niliyotunga mwenyewe",
-       "prefs-common-css-js": "CSS/JS inayoshirikishwa na maumbo yote:",
+       "prefs-common-config": "CSS/JS inayoshirikishwa na maumbo yote:",
        "prefs-reset-intro": "Unaweza kutumia ukurasa huu ili kurudisha mapendekezo yako kwenye yale ya msingi ya tovuti.\nHutaweza kulibatilisha tendo hili baadaye.",
        "prefs-emailconfirm-label": "Kuhakikisha barua pepe:",
        "youremail": "Barua pepe yako:",
index 0ba4ef5..2fa107f 100644 (file)
        "userjspreview": "'''Pamjyntej, aże to je no raźe ino podglůnd Twojego arkusza stylůw JavaScriptu.'''\n'''Ńic jeszcze ńy zostoło naszkryflane!'''",
        "sitecsspreview": "'''Pamjyntej, aże to je na raźe ino podglůnd Twojego arkusza stylůw CSS.'''\n'''Ńic jeszcze ńy zostoło naszkryflane!'''",
        "sitejspreview": "'''Pamjyntej, aże to je na raźe ino podglůnd Twojego JavaScriptu - nic jeszcze ńy zostoło naszkryflane!'''",
-       "userinvalidcssjstitle": "'''Pozůr:''' Ńy mo skůrki uo mjańe \"$1\". Pamjyntej, aże zajty użytkowńika zawjyrajůnce CSS i JavaScript powinny zaczynać śe małům buchsztabům, lb. {{ns:user}}:Foo/vector.css.",
+       "userinvalidconfigtitle": "'''Pozůr:''' Ńy mo skůrki uo mjańe \"$1\". Pamjyntej, aże zajty użytkowńika zawjyrajůnce CSS i JavaScript powinny zaczynać śe małům buchsztabům, lb. {{ns:user}}:Foo/vector.css.",
        "updated": "(Pomjyńano)",
        "note": "'''Pozůr:'''",
        "previewnote": "'''To je ino podglůnd - artikel jeszcze ńy je spamjyntany!'''",
index 979c3f2..655423a 100644 (file)
        "userjspreview": "'''நீர் உமது சாவா நிரலை சோதிக்கிறீர் அல்லது முன் தோற்றத்தை மட்டும் பார்க்கிறீர் என்பதை நினைவில் கொள்ளவும், இன்னமும் சேமிக்கப்படவில்லை!'''",
        "sitecsspreview": "'''நீங்கள் மட்டுமே இந்த CSS. இன் முன் தோற்றத்தை காண்கிறீர்கள் என்பதை நினைவில் கொள்ளவும்.'''\n'''இது இன்னமும் சேமிக்கப்படவில்லை!'''",
        "sitejspreview": "'''நீங்கள் மட்டுமே இந்த JavaScript code இன் முன் தோற்றத்தை காண்கிறீர்கள் என்பதை நினைவில் கொள்ளவும்.'''\n'''இது இன்னமும் சேமிக்கப்படவில்லை!'''",
-       "userinvalidcssjstitle": "'''எச்சரிக்கை:'''  \"$1\" என்றப் பெயரில் தோல்லொறுக் கிடையாது. சி.எஸ்.எஸ் மற்றும் ஜெ.எஸ். பக்கங்கள் ஆங்கில கீழ் வரிசைப் பெயர்களைக் கொண்டிருக்க வேண்டும் என்பதைக் கவனிக்கவும். எ+கா: {{ns:user}}:Foo/vector.css என்பது சரியான வடிவம் {{ns:user}}:Foo/Vector.css என்பது பிழையான வடிவம்.",
+       "userinvalidconfigtitle": "'''எச்சரிக்கை:'''  \"$1\" என்றப் பெயரில் தோல்லொறுக் கிடையாது. சி.எஸ்.எஸ் மற்றும் ஜெ.எஸ். பக்கங்கள் ஆங்கில கீழ் வரிசைப் பெயர்களைக் கொண்டிருக்க வேண்டும் என்பதைக் கவனிக்கவும். எ+கா: {{ns:user}}:Foo/vector.css என்பது சரியான வடிவம் {{ns:user}}:Foo/Vector.css என்பது பிழையான வடிவம்.",
        "updated": "(இற்றைப்படுத்தப்பட்டது)",
        "note": "'''குறிப்பு:'''",
        "previewnote": "'''இது ஒரு முன்தோற்றம் மட்டுமே''', உங்கள் மாற்றங்கள் இன்னும் சேமிக்கப்படவில்லை!",
        "prefs-files": "கோப்புகள்",
        "prefs-custom-css": "தனிப்பட்ட சி.எசு.எசு (CSS)",
        "prefs-custom-js": "தனிபயன் ஜாவாஸ்கிரிப்ட்",
-       "prefs-common-css-js": "எல்லா முகப்புறைகளுக்குமான(skins) பகிரப்பட்ட சி.எசு.எசு/சாவாகிறிப்டு (CSS/JavaScript):",
+       "prefs-common-config": "எல்லா முகப்புறைகளுக்குமான(skins) பகிரப்பட்ட சி.எசு.எசு/சாவாகிறிப்டு (CSS/JavaScript):",
        "prefs-reset-intro": " இந்த பக்கத்தை பயன்படுத்தி உங்கள் விருப்பங்களை தள இயல்புநிலைக்கு மீட்டமைக்கலாம்.\nஇது செய்யாமல் இருக்க இயலாது.",
        "prefs-emailconfirm-label": "மின்னஞ்சலை உறுதிசெய்தல்:",
        "youremail": "மின்னஞ்சல்:",
index 03054cd..08a3884 100644 (file)
        "userjspreview": "<strong>గుర్తుంచుకోండి, మీరింకా మీ వాడుకరి జావాస్క్రిప్ట్&zwnj;ను భద్రపరచలేదు, కేవలం పరీక్షిస్తున్నారు/సరిచూస్తున్నారు!</strong>",
        "sitecsspreview": "'''మీరు చూస్తున్నది ఈ CSS మునుజూపును మాత్రమేనని గుర్తుంచుకోండి.'''\n'''దీన్నింకా భద్రపరచలేదు!'''",
        "sitejspreview": "'''మీరు చూస్తున్నది ఈ JavaScript మునుజూపును మాత్రమేనని గుర్తుంచుకోండి.''' \n'''దీన్నింకా భద్రపరచలేదు!'''",
-       "userinvalidcssjstitle": "<strong>హెచ్చరిక:</strong> \"$1\" అనే రూపు లేదు.\nఅభిమత .css మరియు .js పుటల శీర్షికలు ఇంగ్లీషు చిన్నబడి అక్షరాల లోనే ఉండాలని గుర్తుంచుకోండి, ఉదాహరణకు ఇలా {{ns:user}}:Foo/vector.css అంతేగానీ, {{ns:user}}:Foo/Vector.css ఇలా కాదు.",
+       "userinvalidconfigtitle": "<strong>హెచ్చరిక:</strong> \"$1\" అనే రూపు లేదు.\nఅభిమత .css మరియు .js పుటల శీర్షికలు ఇంగ్లీషు చిన్నబడి అక్షరాల లోనే ఉండాలని గుర్తుంచుకోండి, ఉదాహరణకు ఇలా {{ns:user}}:Foo/vector.css అంతేగానీ, {{ns:user}}:Foo/Vector.css ఇలా కాదు.",
        "updated": "(నవీకరించబడింది)",
        "note": "<strong>గమనిక:</strong>",
        "previewnote": "<strong>ఇది మునుజూపు మాత్రమేనని గుర్తుంచుకోండి.</strong>\nమీ మార్పులు ఇంకా భద్రమవ్వలేదు!",
        "prefs-files": "ఫైళ్ళు",
        "prefs-custom-css": "ప్రత్యేక CSS",
        "prefs-custom-js": "ప్రత్యేక JS",
-       "prefs-common-css-js": "అన్ని రూపులలోనూ ఉన్న CSS/JS:",
+       "prefs-common-config": "అన్ని రూపులలోనూ ఉన్న CSS/JS:",
        "prefs-reset-intro": "ఈ పేజీలో, మీ అభిరుచులను సైటు డిఫాల్టు విలువలకు మార్చుకోవచ్చు. మళ్ళీ వెనక్కి తీసుకుపోలేరు.",
        "prefs-emailconfirm-label": "ఈ-మెయిల్ నిర్ధారణ:",
        "youremail": "ఈమెయిలు:",
index 85d3bf0..03ca342 100644 (file)
        "userjsyoucanpreview": "<strong>Эзоҳ:</strong> Тугмаи \"{{int:showpreview}}\" барои санҷиши парванди ҶаваСкрипт қабл аз захира истифода баред.",
        "usercsspreview": "<strong>Фаромӯш накунед, ки шумо фақат CSS корбариатонро пешнамоиш карда истодааед. Он ҳанӯз захира нашудааст!</strong>",
        "userjspreview": "'''Фаромӯш накунед, ки шумо фақат ҶаваСкрипти корбариатонро имтиҳон,пешнамоиш карда истодаед ва он ҳанӯз захира нашудааст!'''",
-       "userinvalidcssjstitle": "'''Ҳушдор:'''Пӯсте бо номи \"$1\" вуҷуд надорад. Таваҷҷӯҳ кунед ки саҳифаҳои .css ва .js бо ҳарфҳои хурд навишта мешаванд, Намуна. {{ns:user}}:Фу/vector.css дар муқобили корбар {{ns:user}}:Фу/Vector.css.",
+       "userinvalidconfigtitle": "'''Ҳушдор:'''Пӯсте бо номи \"$1\" вуҷуд надорад. Таваҷҷӯҳ кунед ки саҳифаҳои .css ва .js бо ҳарфҳои хурд навишта мешаванд, Намуна. {{ns:user}}:Фу/vector.css дар муқобили корбар {{ns:user}}:Фу/Vector.css.",
        "updated": "(Ба рӯз шуда)",
        "note": "'''Эзоҳ:'''",
        "previewnote": "'''Ба ёд дошта бошед, ки ин фақат пешнамоиш аст.'''\nТағийроти шумо ҳанӯз захира нашудааст!",
index 36e76c3..9171657 100644 (file)
        "usercssyoucanpreview": "'''Ezoh:''' Peş parvandai CSS jo JS xudro zaxira kuned, bo istifoda az tugmai \"Peşnamoiş\" metavoned onro ozmoiş kuned.",
        "userjsyoucanpreview": "'''Ezoh:''' Peş parvandai CSS jo JS xudro zaxira kuned, bo istifoda az tugmai \"Peşnamoiş\" metavoned onro ozmoiş kuned.",
        "userjspreview": "'''Faromūş nakuned, ki şumo faqat ÇavaSkripti korbariatonro imtihon,peşnamoiş karda istodaed va on hanūz zaxira naşudaast!'''",
-       "userinvalidcssjstitle": "'''Huşdor:'''Pūste bo nomi \"$1\" vuçud nadorad. Tavaççūh kuned ki sahifahoi .css va .js bo harfhoi xurd navişta meşavand, Namuna. {{ns:user}}:Fu/vector.css dar muqobili korbar {{ns:user}}:Fu/Vector.css.",
+       "userinvalidconfigtitle": "'''Huşdor:'''Pūste bo nomi \"$1\" vuçud nadorad. Tavaççūh kuned ki sahifahoi .css va .js bo harfhoi xurd navişta meşavand, Namuna. {{ns:user}}:Fu/vector.css dar muqobili korbar {{ns:user}}:Fu/Vector.css.",
        "updated": "(Ba rūz şuda)",
        "note": "'''Ezoh:'''",
        "previewnote": "'''In faqat peşnamoiş ast; digarguniho holo zaxira naşudaand!'''",
index a938047..fac7ea7 100644 (file)
        "userjspreview": "<strong>พึงระลึกว่าคุณกำลังทดสอบ/ดูตัวอย่างจาวาสคริปต์ผู้ใช้ของคุณ\nยังไม่ได้บันทึก!</strong>",
        "sitecsspreview": "<strong>พึงระลึกว่าคุณเพียงกำลังแสดงตัวอย่าง CSS นี้\nยังไม่ได้บันทึก!</strong>",
        "sitejspreview": "<strong>พึงระลึกว่าคุณเพียงกำลังแสดงตัวอย่างโค้ดจาวาสคริปต์นี้\nยังไม่ได้บันทึก!</strong>",
-       "userinvalidcssjstitle": "<strong>คำเตือน:</strong> ไม่มีหน้าตา \"$1\" หน้า .css และ .js ใช้ตัวเล็กทั้งหมด เช่น {{ns:user}}:Foo/vector.css มิใช่ {{ns:user}}:Foo/Vector.css",
+       "userinvalidconfigtitle": "<strong>คำเตือน:</strong> ไม่มีหน้าตา \"$1\" หน้า .css และ .js ใช้ตัวเล็กทั้งหมด เช่น {{ns:user}}:Foo/vector.css มิใช่ {{ns:user}}:Foo/Vector.css",
        "updated": "(ปรับแล้ว)",
        "note": "<strong>หมายเหตุ:</strong>",
        "previewnote": "<strong>พึงระลึกว่านี่เป็นเพียงการแสดงตัวอย่าง</strong>\nยังไม่ได้บันทึกการเปลี่ยนแปลงของคุณ!",
        "prefs-files": "ไฟล์",
        "prefs-custom-css": "สไตล์ชีตปรับแต่งเอง",
        "prefs-custom-js": "จาวาสคริปต์ปรับแต่งเอง",
-       "prefs-common-css-js": "CSS / จาวาสคริปต์รวมสำหรับทุกหน้าตา:",
+       "prefs-common-config": "CSS / จาวาสคริปต์รวมสำหรับทุกหน้าตา:",
        "prefs-reset-intro": "คุณสามารถใช้หน้านี้ตั้งการตั้งค่าของคุณเป็นค่าปริยายของเว็บใหม่\nไม่สามารถทำกลับได้",
        "prefs-emailconfirm-label": "การยืนยันอีเมล:",
        "youremail": "อีเมล:",
index 73e5cd2..0c76518 100644 (file)
        "userjsyoucanpreview": "'''Ümleme:''' Täze JavaScriptiňizi ýazdyrmankaňyz, synap görmek üçin \"{{int:showpreview}}\" düwmesine basyň.",
        "usercsspreview": "'''Bu ulanyjy CSS faýlyňyzyň ýöne bir deslapky syny.'''\n'''Ol heniz ýazdyrylan däldir!'''",
        "userjspreview": "'''Bu ulanyjy JavaScriptiňiziň ýöne bir barlagy/deslapky syny.'''\n'''Ol heniz ýazdyrylan däldir!'''",
-       "userinvalidcssjstitle": "''Duýduryş:''' \"$1\" atly bezeg ýok.\nHususy .css we .js sahypalarynyň setir harp bilen ýazylýandygyny ýatda saklaň, ýagny {{ns:user}}:Ulanyjy/Vector.css däl-de, eýsem {{ns:user}}:Ulanyjy/vector.css.",
+       "userinvalidconfigtitle": "''Duýduryş:''' \"$1\" atly bezeg ýok.\nHususy .css we .js sahypalarynyň setir harp bilen ýazylýandygyny ýatda saklaň, ýagny {{ns:user}}:Ulanyjy/Vector.css däl-de, eýsem {{ns:user}}:Ulanyjy/vector.css.",
        "updated": "(Täzelenen)",
        "note": "'''Bellik:'''",
        "previewnote": "'''Ýatda saklaň, bu bir ýöne deslapky syn.''' Üýtgeşmeleriňiz heniz ýazdyrylan däldir!",
        "prefs-files": "Faýllar",
        "prefs-custom-css": "Hususy CSS",
        "prefs-custom-js": "Hususy JS",
-       "prefs-common-css-js": "Ähli bezegler üçin paýlaşylýan CSS/JavaScript:",
+       "prefs-common-config": "Ähli bezegler üçin paýlaşylýan CSS/JavaScript:",
        "prefs-reset-intro": "Bu sahypada öz ileri tutmalaryňyzy saýtyň gaýybana ýagdaýyna getirip bilersiňiz. Yzyna dikeldip bolmaýar.",
        "prefs-emailconfirm-label": "E-poçta tassyklamasy:",
        "youremail": "E-poçta:",
index 9d822ad..bbaa125 100644 (file)
        "userjspreview": "'''Tandaang pagsubok/paunang tingin mo pa lang ito ng iyong JavaScript.'''\n'''Hindi pa ito nasasagip!'''",
        "sitecsspreview": "'''Tandaan mong paunang tingin pa lamang ito ng nasabing CSS.'''\n'''Hindi pa ito nasasagip!'''",
        "sitejspreview": "'''Tandaan mong paunang tingin pa lamang ito ng nasabing kodigong JavaScript.'''\n'''Hindi pa ito nasasagip!'''",
-       "userinvalidcssjstitle": "'''Babala:''' Walang pabalat na \"$1\".\nTandaang gumagamit ang pinasadyang mga pahinang .css at .js ng mga pamagat na may maliliit na mga titik, halimbawa na ang {{ns:user}}:Foo/vector.css na taliwas sa {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Babala:''' Walang pabalat na \"$1\".\nTandaang gumagamit ang pinasadyang mga pahinang .css at .js ng mga pamagat na may maliliit na mga titik, halimbawa na ang {{ns:user}}:Foo/vector.css na taliwas sa {{ns:user}}:Foo/Vector.css.",
        "updated": "(Naisapanahon na)",
        "note": "'''Paunawa:'''",
        "previewnote": "'''Tandaan na isa lamang itong paunang tingin.'''\nHindi pa nasasagip ang mga binago mo!",
        "prefs-files": "Mga file",
        "prefs-custom-css": "Pasadyang CSS",
        "prefs-custom-js": "Pasadyang JS",
-       "prefs-common-css-js": "Naibahaging CSS/JS para sa lahat ng pabalat:",
+       "prefs-common-config": "Naibahaging CSS/JS para sa lahat ng pabalat:",
        "prefs-reset-intro": "Magagamit mo ang pahinang ito upang muling maitakda ang mga kagustuhan mo sa likas na pagtatakda ng sityo.\nHindi ito maibabalik sa dating gawi.",
        "prefs-emailconfirm-label": "Kumpirmasyon ng e-liham:",
        "youremail": "E-liham:",
index bf16795..206fc09 100644 (file)
        "userjspreview": "'''Sadece test ediyorsun ya da önizleme görüyorsun - kullanıcı JavaScript'i henüz kaydolmadı.'''",
        "sitecsspreview": "'''Sadece kullanıcı CSS dosyanızın önizlemesini görüyorsunuz.''' \n'''Henüz kaydedilmedi!'''",
        "sitejspreview": "'''Sadece kullanıcı JavaScript kod dosyanızın önizlemesini görüyorsunuz.''' \n'''Henüz kaydedilmedi!'''",
-       "userinvalidcssjstitle": "'''Uyarı:''' \"$1\" adında bir tema yoktur. Özel .css ve .js sayfalarının adlarını küçük harf ile yazın, örneğin;  \"{{ns:user}}:Örnek/Vector.css\" yerine \"{{ns:user}}:Örnek/vector.css\" yazın.",
+       "userinvalidconfigtitle": "'''Uyarı:''' \"$1\" adında bir tema yoktur. Özel .css ve .js sayfalarının adlarını küçük harf ile yazın, örneğin;  \"{{ns:user}}:Örnek/Vector.css\" yerine \"{{ns:user}}:Örnek/vector.css\" yazın.",
        "updated": "(Güncellendi)",
        "note": "'''Not: '''",
        "previewnote": "'''Bunun yalnızca bir ön izleme olduğunu unutmayın.'''\nYaptığınız değişiklikler henüz kaydedilmedi!",
        "prefs-files": "Dosyalar",
        "prefs-custom-css": "Özel CSS",
        "prefs-custom-js": "Özel JS",
-       "prefs-common-css-js": "Tüm temalar için paylaşılan CSS/JS:",
+       "prefs-common-config": "Tüm temalar için paylaşılan CSS/JS:",
        "prefs-reset-intro": "Bu sayfayı tercihlerinizi site varsayılanına döndürmek için kullanabilirsiniz. Bu geri alınamaz.",
        "prefs-emailconfirm-label": "E-posta doğrulaması:",
        "youremail": "E-posta:",
index fe6c9bf..70ebecb 100644 (file)
        "userjspreview": "'''Бу бары тик JavaScript файлын алдан карау гына, ул әле сакланмаган!'''",
        "sitecsspreview": "'''онытмагыз, бу бары тик CSS-файлны алдан карау гына.'''\n'''Ул әле сакланмаган!'''",
        "sitejspreview": "'''Бу бары тик JavaScript файлын алдан карау гына.'''\n'''Ул әле сакланмаган!'''",
-       "userinvalidcssjstitle": "'''Игътибар:''' \"$1\" бизәү темасы табылмады. Кулланучының .css һәм .js битләре исемнәре бары тик кечкенә (юл) хәрефләрдән генә торырга тиеш икәнен онытмагыз. Мисалга: {{ns:user}}:Foo/vector.css, ә {{ns:user}}:Foo/Vector.css түгел!",
+       "userinvalidconfigtitle": "'''Игътибар:''' \"$1\" бизәү темасы табылмады. Кулланучының .css һәм .js битләре исемнәре бары тик кечкенә (юл) хәрефләрдән генә торырга тиеш икәнен онытмагыз. Мисалга: {{ns:user}}:Foo/vector.css, ә {{ns:user}}:Foo/Vector.css түгел!",
        "updated": "(Яңартылды)",
        "note": "'''Искәрмә:'''",
        "previewnote": "<strong>Исегездә тотыгыз, бу алдан карау гына.</strong>\nТәзәтмәләрегез әлегә сакланмаган!",
        "prefs-files": "Файллар",
        "prefs-custom-css": "Шәхси CSS",
        "prefs-custom-js": "Шәхси JS",
-       "prefs-common-css-js": "Барлык бизәлешләр өчен гомуми CSS/JS:",
+       "prefs-common-config": "Барлык бизәлешләр өчен гомуми CSS/JS:",
        "prefs-reset-intro": "Бу бит сезнең көйләнмәләрегезне бетерү өчен кулланыла. Бу эшне башкару нәтиҗәсендә сез яңадан үз көйләнмәләрне яңадан кайтара алмыйсыз.",
        "prefs-emailconfirm-label": "E-mail раслау",
        "youremail": "Электрон почта:",
index 91cf0b4..8b774ed 100644 (file)
        "userjsyoucanpreview": "'''Yärdäm:''' \"{{int:showpreview}}\" töymäsenä basıp, yaña JS-faylnı tikşerep bula.",
        "usercsspreview": "'''Bu barı tik CSS-faylnı aldan qaraw ğına, ul äle saqlanmağan!'''",
        "userjspreview": "'''Bu barı tik JavaScript faylın aldan qaraw ğına, ul äle saqlanmağan!'''",
-       "userinvalidcssjstitle": "'''İğtibar:''' \"$1\" bizäw teması tabılmadı. Qullanuçınıñ .css häm .js bitläre isemnäre barı tik keçkenä (yul) xäreflärdän genä torırğa tieş ikänen onıtmağız. Misalğa: {{ns:user}}:Foo/vector.css, ä {{ns:user}}:Foo/Vector.css tügel!",
+       "userinvalidconfigtitle": "'''İğtibar:''' \"$1\" bizäw teması tabılmadı. Qullanuçınıñ .css häm .js bitläre isemnäre barı tik keçkenä (yul) xäreflärdän genä torırğa tieş ikänen onıtmağız. Misalğa: {{ns:user}}:Foo/vector.css, ä {{ns:user}}:Foo/Vector.css tügel!",
        "updated": "(Yañartıldı)",
        "note": "'''İskärmä:'''",
        "previewnote": "'''Bu fäqät aldan qaraw ğına, üzgärtüläregez äle saqlanmağan!'''",
        "prefs-files": "Fayllar",
        "prefs-custom-css": "Üzemneñ CSS",
        "prefs-custom-js": "Üzemneñ JS",
-       "prefs-common-css-js": "Barlıq bizäleşlär öçen ğomumi CSS/JS:",
+       "prefs-common-config": "Barlıq bizäleşlär öçen ğomumi CSS/JS:",
        "prefs-reset-intro": "Bu bit sezneñ köylänmäläregezne beterü öçen qullanıla. Bu eşne başqaru näticäsendä sez yañadan üz köylänmälärne yañadan qaytara almıysız.",
        "prefs-emailconfirm-label": "E-mail raslaw",
        "youremail": "Elektron poçta:",
index 706c3a4..0a48ddc 100644 (file)
        "userjspreview": "'''دىققەت سىز پەقەت ئۆزىڭىزنىڭ شەخسىي JavaScript نى ئالدىن كۆزىتىۋاتىسىز/سىناۋاتىسىز.'''\n'''ئۇ تېخى ساقلانمىدى!'''",
        "sitecsspreview": "'''دىققەت سىز پەقەت بۇ CSS نى ئالدىن كۆزىتىۋاتىسىز.'''\n'''ئۇ تېخى ساقلانمىدى!'''",
        "sitejspreview": "'''دىققەت سىز پەقەت بۇ JavaScript كودنى ئالدىن كۆزىتىۋاتىسىز.'''\n'''ئۇ تېخى ساقلانمىدى!'''",
-       "userinvalidcssjstitle": "'''ئاگاھلاندۇرۇش:''' تېرە\\\"$1\" مەۋجۇد ئەمەس.\nئادەتلەنگەن .css ۋە .js تور بەت ماۋزۇسىغا كىچىك يېزىلىشتىكى ھەرپ ئىشلىتىلىدۇ، مەسىلەن، {{ns:user}}:Foo/vector.css بىلەن {{ns:user}}:Foo/Vector.css ئوخشاش ئەمەس.",
+       "userinvalidconfigtitle": "'''ئاگاھلاندۇرۇش:''' تېرە\\\"$1\" مەۋجۇد ئەمەس.\nئادەتلەنگەن .css ۋە .js تور بەت ماۋزۇسىغا كىچىك يېزىلىشتىكى ھەرپ ئىشلىتىلىدۇ، مەسىلەن، {{ns:user}}:Foo/vector.css بىلەن {{ns:user}}:Foo/Vector.css ئوخشاش ئەمەس.",
        "updated": "(يېڭىلاندى)",
        "note": "'''ئىزاھات:'''",
        "previewnote": "'''ئېسىڭىزدە بولسۇنكى بۇ پەقەتلا ئالدىن كۆزىتىش.'''\nئۆزگەرتكەن مەزمۇنىڭىز تېخى ساقلانمىدى!",
        "prefs-files": "ھۆججەتلەر",
        "prefs-custom-css": "ئىختىيارى CSS",
        "prefs-custom-js": "ئىختىيارى JS",
-       "prefs-common-css-js": "ھەممە تېرىدە ھەمبەھىرلەنگەن CSS/JS:",
+       "prefs-common-config": "ھەممە تېرىدە ھەمبەھىرلەنگەن CSS/JS:",
        "prefs-reset-intro": "سىز بۇ بەتنى ئىشلىتىپ تەڭشەكلىرىڭىزنى تور بېكەتنىڭ كۆڭۈلدىكى قىممىتىگە تەڭشىيەلەيسىز.\nبۇ مەشغۇلاتتىن يانغىلى بولمايدۇ.",
        "prefs-emailconfirm-label": "ئېلخەت جەزملەش:",
        "youremail": "ئېلخەت:",
index 81c211f..703fa0d 100644 (file)
        "userjspreview": "'''Пам'ятайте, що це тільки попередній перегляд вашого JavaScript-файлу і він поки-що не збережений!'''",
        "sitecsspreview": "'''Пам'ятайте, що це тільки попередній перегляд цього CSS.'''\n'''Його ще не збережено!'''",
        "sitejspreview": "'''Пам'ятайте, що це лише попередній перегляд вашого JavaScript-коду.'''\n'''Його ще не збережено!'''",
-       "userinvalidcssjstitle": "'''Увага:''' тема оформлення «$1» не знайдена.\nПам'ятайте, що користувацькі .css та .js сторінки повинні мати назву, що складається лише з малих літер, наприклад «{{ns:user}}:Хтось/vector.css», а не «{{ns:user}}:Хтось/Vector.css».",
+       "userinvalidconfigtitle": "'''Увага:''' тема оформлення «$1» не знайдена.\nПам'ятайте, що користувацькі .css та .js сторінки повинні мати назву, що складається лише з малих літер, наприклад «{{ns:user}}:Хтось/vector.css», а не «{{ns:user}}:Хтось/Vector.css».",
        "updated": "(Оновлена)",
        "note": "'''Зауваження:'''",
        "previewnote": "'''Це лише попередній перегляд.'''\nВаші зміни ще не збережено!",
        "prefs-files": "Файли",
        "prefs-custom-css": "Власний CSS",
        "prefs-custom-js": "Власний JS",
-       "prefs-common-css-js": "CSS/JS спільні для всіх тем оформлення:",
+       "prefs-common-config": "CSS/JS спільні для всіх тем оформлення:",
        "prefs-reset-intro": "Ця сторінка може бути використана для зміни ваших налаштувань на стандартні.\nПісля виконання цієї дії ви не зможете відкотити зміни.",
        "prefs-emailconfirm-label": "Підтвердження адреси:",
        "youremail": "Адреса електронної пошти:",
index e33c545..fbfb888 100644 (file)
        "userjspreview": "<strong>یاد رہے کہ اس وقت آپ اپنی جاوا اسکرپٹ کی محض نمائش دیکھ/جانچ رہے ہیں، یہ اب تک محفوظ نہیں ہوئی ہے!</strong>",
        "sitecsspreview": "<strong>یاد رہے کہ اس وقت آپ اس سی ایس کی محض نمائش دیکھ رہے ہیں، یہ اب تک محفوظ نہیں ہوئی ہے!</strong>",
        "sitejspreview": "<strong>یاد رہے کہ اس وقت آپ اس جاوا اسکرپٹ کوڈ کی محض نمائش دیکھ رہے ہیں، یہ اب تک محفوظ نہیں ہوئی ہے!</strong>",
-       "userinvalidcssjstitle": "<strong>انتباہ:</strong> یہاں «$1» نام سے کوئی پوشاک موجود نہیں۔ شخصی .css اور .js کے صفحات اپنے عنوان میں چھوٹے حروف استعمال کرتے ہیں، مثلاً {{ns:user}}:Foo/Vector.css کی بجائے {{ns:user}}:Foo/vector.css",
+       "userinvalidconfigtitle": "<strong>انتباہ:</strong> یہاں «$1» نام سے کوئی پوشاک موجود نہیں۔ شخصی .css اور .js کے صفحات اپنے عنوان میں چھوٹے حروف استعمال کرتے ہیں، مثلاً {{ns:user}}:Foo/Vector.css کی بجائے {{ns:user}}:Foo/vector.css",
        "updated": "(اپ ڈیٹڈ)",
        "note": "'''نوٹ:'''",
        "previewnote": "'''یاد رکھیں، یہ صرف نمائش ہے ۔آپ کی ترامیم ابھی محفوظ نہیں کی گئیں۔'''",
        "prefs-files": "فائلیں",
        "prefs-custom-css": "شخصی سی ایس ایس",
        "prefs-custom-js": "شخصی جاوا اسکرپٹ",
-       "prefs-common-css-js": "جملہ پوشاکوں کے لیے مشترکہ سی ایس ایس/جاوا اسکرپٹ:",
+       "prefs-common-config": "جملہ پوشاکوں کے لیے مشترکہ سی ایس ایس/جاوا اسکرپٹ:",
        "prefs-reset-intro": "آپ اس صفحہ کے ذریعہ اپنی موجودہ ترجیحات کو سائٹ کی ابتدائی ترتیبات کے مطابق ڈھال سکتے ہیں۔\nلیکن اسے واپس نہیں پھیرا جا سکتا۔",
        "prefs-emailconfirm-label": "برقی خط کی تصدیق:",
        "youremail": "برقی خط:",
index 7481a98..a5f3d41 100644 (file)
        "prefs-files": "Fayllar",
        "prefs-custom-css": "Shaxsiy CSS",
        "prefs-custom-js": "Shaxsiy JavaScript",
-       "prefs-common-css-js": "Umumiy CSS/JavaScript (barcha tashqi koʻrinishlar uchun):",
+       "prefs-common-config": "Umumiy CSS/JavaScript (barcha tashqi koʻrinishlar uchun):",
        "prefs-emailconfirm-label": "Elektron pochta manzilini tasdiqlash:",
        "youremail": "E-mail:",
        "username": "Foydalanuvchi nomi",
index b4b772b..f18227e 100644 (file)
        "userjspreview": "'''Sta qua la xe solo n'anteprima par proar el proprio JavaScript personal; le modifiche no le xe gnancora stà salvà!'''",
        "sitecsspreview": "'''Sta qua la xe solo n'anteprima del proprio CSS personal. Le modifiche no le xe gnancora stà salvà!'''",
        "sitejspreview": "'''Sta qua la xe solo n'anteprima par proar el proprio JavaScript personal; le modifiche no le xe gnancora stà salvà!'''",
-       "userinvalidcssjstitle": "'''Ocio:'''  No ghe xe nissuna skin con nome \"$1\". Nota che le pagine par i .css e .js personalizà le gà l'iniziale del titolo minuscola, par esenpio {{ns:user}}:Esenpio/vector.css e no {{ns:user}}:Esenpio/Vector.css.",
+       "userinvalidconfigtitle": "'''Ocio:'''  No ghe xe nissuna skin con nome \"$1\". Nota che le pagine par i .css e .js personalizà le gà l'iniziale del titolo minuscola, par esenpio {{ns:user}}:Esenpio/vector.css e no {{ns:user}}:Esenpio/Vector.css.",
        "updated": "(Agiornà)",
        "note": "'''Nota:'''",
        "previewnote": "'''Tiente in mente che sta qua la xe solo n'anteprima.'''\nI to canbiamenti NO i xe gnancora stà salvài!",
        "prefs-files": "File",
        "prefs-custom-css": "CSS personalixà",
        "prefs-custom-js": "JS personalixà",
-       "prefs-common-css-js": "CSS/JS condiviso par tute łe skin:",
+       "prefs-common-config": "CSS/JS condiviso par tute łe skin:",
        "prefs-reset-intro": "Te pol doparar sta pagina par riportar le to preferense a quele predefinìe.\nSta operassion no la pol èssar anulà.",
        "prefs-emailconfirm-label": "Conferma de l'e-mail:",
        "youremail": "La to e-mail",
index 61c6728..bcb21b1 100644 (file)
        "prefs-files": "Failad",
        "prefs-custom-css": "Ičeze CSS",
        "prefs-custom-js": "Ičeze JS",
-       "prefs-common-css-js": "Ühthižed CSS/JavaScript kaikiden temiden täht:",
+       "prefs-common-config": "Ühthižed CSS/JavaScript kaikiden temiden täht:",
        "prefs-reset-intro": "Tö sat kävutada nece lehtpol', miše pördutada teiden järgendused saitan ezijärgendusidennoks.\nNecidä tegendad ei sa toižetada.",
        "prefs-emailconfirm-label": "E-počtan vahvištand:",
        "youremail": "E-počt:",
index 84c0a47..bb064d8 100644 (file)
        "anonpreviewwarning": "''Bạn chưa đăng nhập. Khi lưu trang này, địa chỉ IP của bạn sẽ được ghi vào lịch sử trang.''",
        "missingsummary": "'''Nhắc nhở:''' Bạn đã không ghi lại tóm lược sửa đổi. Nếu bạn nhấn Lưu trang một lần nữa, sửa đổi của bạn sẽ được lưu mà không có tóm lược.",
        "selfredirect": "<strong>Cảnh báo:</strong> Bạn sắp đổi hướng trang này đến chính trang này.\nCó lẽ bạn đã định rõ mục tiêu sai hoặc bạn đang sửa trang sai.\nNếu bạn bấm “$1” lần nữa, trang đổi hướng sẽ được tạo ra.",
-       "missingcommenttext": "Xin hãy gõ vào lời bàn luận ở dưới.",
+       "missingcommenttext": "Xin hãy nhập bình luận vào đây.",
        "missingcommentheader": "<strong>Nhắc nhở:</strong> Bạn chưa ghi chủ đề/tiêu đề cho bàn luận này.\nNếu bạn nhấn nút “$1” lần nữa, sửa đổi của bạn sẽ được lưu mà không có đề mục.",
        "summary-preview": "Xem trước dòng tóm lược sửa đổi:",
        "subject-preview": "Xem trước đề mục:",
        "userjspreview": "'''Nhớ rằng bạn chỉ đang kiểm thử/xem trước trang JavaScript, nó chưa được lưu!'''",
        "sitecsspreview": "'''Nhớ rằng bạn chỉ đang xem trước bản CSS này.'''\n'''Nó chưa được lưu!'''",
        "sitejspreview": "'''Nhớ rằng bạn chỉ đang xem trước bản JavaScript này.\n'''Nó chưa được lưu!'''",
-       "userinvalidcssjstitle": "'''Cảnh báo:''' Không có skin “$1”. Hãy nhớ rằng các trang .css và .js tùy chỉnh sử dụng tiêu đề chữ thường, như {{ns:user}}:Ví&nbsp;dụ/vector.css chứ không phải {{ns:user}}:Ví&nbsp;dụ/Vector.css.",
+       "userinvalidconfigtitle": "'''Cảnh báo:''' Không có skin “$1”. Hãy nhớ rằng các trang .css và .js tùy chỉnh sử dụng tiêu đề chữ thường, như {{ns:user}}:Ví&nbsp;dụ/vector.css chứ không phải {{ns:user}}:Ví&nbsp;dụ/Vector.css.",
        "updated": "(Cập nhật)",
        "note": "'''Ghi chú:'''",
        "previewnote": "'''Đây chỉ mới là bản xem trước.'''\nCác thay đổi của bạn vẫn chưa được lưu!",
        "yourtext": "Nội dung bạn nhập",
        "storedversion": "Phiên bản lưu",
        "editingold": "'''Chú ý: bạn đang sửa một phiên bản cũ. Nếu bạn lưu, các sửa đổi trên các phiên bản mới hơn sẽ bị mất.'''",
+       "unicode-support-fail": "Trình duyệt của bạn không hỗ trợ Unicode. Đây là yêu cầu bắt buộc nếu bạn muốn sửa đổi trang tại đây, do đó thay đổi của bạn không được lưu.",
        "yourdiff": "Khác",
        "copyrightwarning": "Xin chú ý rằng tất cả các đóng góp của bạn tại {{SITENAME}} được xem là sẽ phát hành theo giấy phép $2 (xem $1 để biết thêm chi tiết). Nếu bạn không muốn những gì mình viết ra bị sửa đổi không thương tiếc và không sẵn lòng cho phép phát hành lại, xin đừng nhấn nút \"Lưu trang\".<br />\nBạn phải đảm bảo với chúng tôi rằng chính bạn là tác giả của những gì mình viết ra, hoặc chép nó từ một nguồn thuộc phạm vi công cộng hoặc tự do tương đương.<br />\n<strong>ĐỪNG ĐĂNG NỘI DUNG CÓ BẢN QUYỀN MÀ CHƯA XIN PHÉP!</strong>",
        "copyrightwarning2": "Xin chú ý rằng tất cả các đóng góp của bạn tại {{SITENAME}} có thể được sửa đổi, thay thế, hoặc xóa bỏ bởi các thành viên khác. Nếu bạn không muốn trang của bạn bị sửa đổi không thương tiếc, đừng đăng trang ở đây.<br />\nBạn phải đảm bảo với chúng tôi rằng chính bạn là người viết nên, hoặc chép nó từ một nguồn thuộc phạm vi công cộng hoặc tự do tương đương (xem $1 để biết thêm chi tiết).\n'''Đừng đăng nội dung có bản quyền mà không xin phép!'''",
        "postedit-confirmation-created": "Trang đã được tạo ra.",
        "postedit-confirmation-restored": "Trang đã được phục hồi.",
        "postedit-confirmation-saved": "Sửa đổi của bạn đã được lưu.",
+       "postedit-confirmation-published": "Thay đổi của bạn đã được xuất bản.",
        "edit-already-exists": "Không thể tạo trang mới.\nNó đã tồn tại.",
        "defaultmessagetext": "Nội dung mặc định",
        "content-failed-to-parse": "Thất bại phân tích nội dung $2 cho kiểu $1: $3",
        "parser-template-loop-warning": "Phát hiện bản mẫu lặp vòng: [[$1]]",
        "template-loop-category": "Trang có bản mẫu lặp vòng",
        "template-loop-category-desc": "Trang chứa một hoặc nhiều bản mẫu lặp vòng, tức là những bản mẫu tự gọi đệ quy chính nó.",
+       "template-loop-warning": "<strong>Cảnh báo:</strong> Trang này gọi [[:$1]] tạo ra vòng lặp bản mẫu (vòng gọi đệ quy vô hạn).",
        "parser-template-recursion-depth-warning": "Bản mẫu đã vượt quá giới hạn về độ sâu đệ quy ($1)",
        "language-converter-depth-warning": "Đã vượt quá giới hạn độ sâu của bộ chuyển đổi ngôn ngữ ($1)",
        "node-count-exceeded-category": "Trang có số nốt vượt quá giới hạn cho phép",
        "recentchangesdays-max": "(tối đa $1 ngày)",
        "recentchangescount": "Số sửa đổi hiển thị mặc định:",
        "prefs-help-recentchangescount": "Số này bao gồm các thay đổi gần đây, lịch sử trang, và nhật trình.",
-       "prefs-help-watchlist-token2": "Đây là chìa khóa bí mật cho nguồn cấp dữ liệu danh sách theo dõi của bạn.\nBất cứ ai biết nó sẽ có thể để đọc danh sách theo dõi của bạn, vì vậy đừng chia sẻ nó.\n[[Special:ResetTokens|Nhấn chuột vào đây nếu bạn cần phải thiết lập lại nó]].",
+       "prefs-help-watchlist-token2": "Đây là chìa khóa bí mật cho nguồn cấp dữ liệu danh sách theo dõi của bạn.\nBất cứ ai biết nó sẽ có thể để đọc danh sách theo dõi của bạn, vì vậy đừng chia sẻ nó.\nNếu cần, [[Special:ResetTokens|bạn có thể thiết lập lại nó]].",
        "savedprefs": "Đã lưu các tùy chọn cá nhân.",
        "savedrights": "Đã lưu các nhóm người dùng của {{GENDER:$1}}$1.",
        "timezonelegend": "Múi giờ:",
        "timezoneregion-europe": "Châu Âu",
        "timezoneregion-indian": "Ấn Độ Dương",
        "timezoneregion-pacific": "Thái Bình Dương",
-       "allowemail": "Nhận thư điện tử từ các thành viên khác",
+       "allowemail": "Cho phép các thành viên khác gửi thư điện tử cho tôi",
+       "email-allow-new-users-label": "Nhận thư điện tử từ các thành viên mới",
+       "email-blacklist-label": "Cấm các thành viên sau gửi thư điện tử cho tôi:",
        "prefs-searchoptions": "Tìm kiếm",
        "prefs-namespaces": "Không gian tên",
        "default": "mặc định",
        "prefs-files": "Tập tin",
        "prefs-custom-css": "sửa CSS",
        "prefs-custom-js": "sửa JS",
-       "prefs-common-css-js": "CSS/JavaScript chung cho mọi giao diện:",
+       "prefs-common-config": "CSS/JavaScript chung cho mọi giao diện:",
        "prefs-reset-intro": "Có thể mặc định lại toàn bộ tùy chọn dùng trang này. Điều này không thể hoàn tác.",
        "prefs-emailconfirm-label": "Xác nhận thư điện tử:",
        "youremail": "Thư điện tử:",
        "recentchanges-legend": "Tùy chọn thay đổi gần đây",
        "recentchanges-summary": "Xem các thay đổi gần đây nhất trên wiki này tại đây.",
        "recentchanges-noresult": "Không có thay đổi trong khoảng thời gian phù hợp với các tiêu chí này.",
+       "recentchanges-timeout": "Yêu cầu tìm kiếm này đã bị quá hạn. Bạn có thể thử sử dụng các tham số tìm kiếm khác.",
+       "recentchanges-network": "Không thể tải kết quả do lỗi kĩ thuật. Xin hãy làm mới lại trang.",
+       "recentchanges-notargetpage": "Nhập tên trang vào ô trên để xem các thay đổi có liên quan tới trang đó.",
        "recentchanges-feed-description": "Theo dõi các thay đổi gần đây nhất của wiki dùng nguồn cấp dữ liệu này.",
        "recentchanges-label-newpage": "Bản sửa này tạo ra trang mới",
        "recentchanges-label-minor": "Đây là một sửa đổi nhỏ",
        "rcfilters-group-results-by-page": "Nhóm kết quả theo trang",
        "rcfilters-activefilters": "Bộ lọc hiện hành",
        "rcfilters-advancedfilters": "Bộ lọc nâng cao",
-       "rcfilters-limit-title": "Số kết quả để hiển thị",
+       "rcfilters-limit-title": "Số kết quả hiển thị",
+       "rcfilters-limit-and-date-label": "$1 thay đổi, $2",
        "rcfilters-days-title": "Những ngày gần đây",
        "rcfilters-hours-title": "Số giờ gần đây",
        "rcfilters-days-show-days": "$1 ngày",
        "rcfilters-days-show-hours": "$1 giờ",
        "rcfilters-highlighted-filters-list": "Tô màu: $1",
        "rcfilters-quickfilters": "Bộ lọc đã lưu",
-       "rcfilters-quickfilters-placeholder-title": "Chưa lưu liên kết",
+       "rcfilters-quickfilters-placeholder-title": "Chưa lưu các bộ lọc",
        "rcfilters-quickfilters-placeholder-description": "Để lưu thiết lập bộ lọc để dùng lại sau, bấm hình dấu trang trong hộp “Bộ lọc hiện hành” bên dưới.",
        "rcfilters-savedqueries-defaultlabel": "Bộ lọc đã lưu",
        "rcfilters-savedqueries-rename": "Đổi tên",
        "rcfilters-view-namespaces-tooltip": "Lọc kết quả theo không gian tên",
        "rcfilters-view-tags-tooltip": "Lọc kết quả theo thẻ đánh dấu",
        "rcfilters-view-return-to-default-tooltip": "Quay lại trình đơn bộ lọc chính",
+       "rcfilters-view-tags-help-icon-tooltip": "Tìm hiểu thêm về các sửa đổi bị đánh dấu",
        "rcfilters-liveupdates-button": "Cập nhật trực tiếp",
        "rcfilters-liveupdates-button-title-on": "Tắt cập nhật trực tiếp",
        "rcfilters-liveupdates-button-title-off": "Hiển thị các thay đổi mới lúc khi xảy ra",
        "rcfilters-watchlist-markseen-button": "Đánh dấu tất cả thay đổi là đã xem",
        "rcfilters-watchlist-edit-watchlist-button": "Sửa danh sách trang theo dõi",
        "rcfilters-watchlist-showupdated": "Thay đổi mới trên các trang kể lần cuối bạn xem trang được in <strong>đậm</strong> và có dấu tô màu.",
+       "rcfilters-preference-label": "Ẩn phiên bản cải tiến của trang Thay đổi Gần đây",
+       "rcfilters-target-page-placeholder": "Nhập tên trang (hoặc thể loại)",
        "rcnotefrom": "Dưới đây là {{PLURAL:$5|thay đổi duy nhất|các thay đổi}} từ <strong>$3 $4</strong> (hiển thị tối đa <strong>$1</strong> thay đổi).",
        "rclistfromreset": "Đặt lại lựa chọn ngày",
        "rclistfrom": "Xem các thay đổi từ $2 $3 trở về sau",
        "file-deleted-duplicate-notitle": "Một tập tin giống hệt như tập tin này đã từng bị xóa và tên bị xóa hẳn trước đây.\nBạn nên xin một người có quyền xem dữ liệu tập tin bị xóa hẳn xem lại trường hợp này trước khi tiếp tục tải nó lên lại.",
        "uploadwarning": "Cảnh báo!",
        "uploadwarning-text": "Xin hãy chỉnh sửa miêu tả tập tin ở dưới và thử lại.",
+       "uploadwarning-text-nostash": "Xin hãy tải lại tập tin lên, sửa đổi phần mô tả và thử lại.",
        "savefile": "Lưu tập tin",
        "uploaddisabled": "Chức năng tải lên đã bị khóa.",
        "copyuploaddisabled": "Chức năng tải lên từ địa chỉ URL đã bị tắt.",
        "uploadstash-refresh": "Làm mới danh sách tập tin",
        "uploadstash-thumbnail": "xem hình thu nhỏ",
        "uploadstash-exception": "Không thể lưu tập tin vào hàng đợi tải lên ($1): “$2”.",
+       "uploadstash-bad-path": "Đường dẫn không tồn tại.",
+       "uploadstash-bad-path-invalid": "Đường dẫn không hợp lệ.",
+       "uploadstash-bad-path-unknown-type": "Loại không xác định \"$1\".",
+       "uploadstash-bad-path-unrecognized-thumb-name": "Tên hình thu nhỏ không nhận dạng được.",
+       "uploadstash-file-not-found-no-thumb": "Không thể tải hình thu nhỏ.",
+       "uploadstash-file-not-found-no-object": "Không tạo được đối tượng tập tin cục bộ cho hình thu nhỏ.",
+       "uploadstash-file-not-found-no-remote-thumb": "Nạp hình thu nhỏ thất bại: $1\nURL = $2",
+       "uploadstash-not-logged-in": "Người dùng chưa đăng nhập, các tập tin phải do người dùng đã đăng nhập tải lên.",
+       "uploadstash-no-such-key": "Khoá không tồn tại ($1), không thể xoá.",
+       "uploadstash-zero-length": "Tập tin có dung lượng bằng không.",
        "invalid-chunk-offset": "Khúc lệch (chunk offset) không hợp lệ",
        "img-auth-accessdenied": "Không cho phép truy cập",
        "img-auth-nopathinfo": "Thiếu PATH_INFO.\nMáy chủ của bạn không được thiết lập để truyền thông tin này.\nCó thể do nó dựa trên CGI và không hỗ trợ img_auth.\nXem [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Image_Authorization hướng dẫn điều khiển truy cập hình ảnh].",
        "thumbnail_dest_directory": "Không thể tạo thư mục đích",
        "thumbnail_image-type": "Không hỗ trợ kiểu hình này",
        "thumbnail_gd-library": "Cấu hình thư viện GD chưa hoàn thành: thiếu hàm $1",
+       "thumbnail_image-size-zero": "Dung lượng tập tin ảnh bằng không.",
        "thumbnail_image-missing": "Hình như tập tin mất tích: $1",
        "thumbnail_image-failure-limit": "Việc tạo ra hình thu nhỏ này đã bị thất bại nhiều lần quá gần đây ($1 lần trở lên). Xin vui lòng thử lại sau.",
        "import": "Nhập các trang",
        "import-mapping-namespace": "Nhập vào một không gian tên:",
        "import-mapping-subpage": "Nhập thành các trang con của trang sau:",
        "import-upload-filename": "Tên tập tin:",
+       "import-upload-username-prefix": "Tiền tố liên wiki:",
+       "import-assign-known-users": "Gán sửa đổi cho thành viên cục bộ nếu tồn tại thành viên có tên tương tự",
        "import-comment": "Lý do:",
        "importtext": "Xin hãy xuất tập tin từ wiki nguồn dùng [[Special:Export|công cụ xuất]].\nLưu nó vào máy tính của bạn rồi tải nó lên đây.",
        "importstart": "Đang nhập các trang…",
        "imported-log-entries": "Đã nhập {{PLURAL:$1|mục nhật trình|$1 mục nhật trình}}.",
        "importfailed": "Không nhập được: $1",
        "importunknownsource": "Không hiểu nguồn trang để nhập vào",
+       "importnoprefix": "Chưa điền tiền tố liên wiki",
        "importcantopen": "Không thể mở tập tin để nhập vào",
        "importbadinterwiki": "Liên kết liên wiki sai",
        "importsuccess": "Nhập thành công!",
        "pageinfo-category-subcats": "Số thể loại con",
        "pageinfo-category-files": "Số tập tin",
        "pageinfo-user-id": "ID người dùng",
+       "pageinfo-file-hash": "Giá trị băm",
        "markaspatrolleddiff": "Đánh dấu tuần tra",
        "markaspatrolledtext": "Đánh dấu tuần tra trang này",
        "markaspatrolledtext-file": "Đánh dấu đã tuần tra phiên bản file này",
        "autosumm-blank": "Đã tẩy trống trang",
        "autosumm-replace": "Đã thay thế cả nội dung bằng “$1”",
        "autoredircomment": "Đổi hướng đến [[$1]]",
+       "autosumm-removed-redirect": "Xoá đổi hướng đến trang [[$1]]",
+       "autosumm-changed-redirect-target": "Thay đổi trang đích của đổi hướng từ [[$1]] sang [[S2]]",
        "autosumm-new": "Tạo trang mới với nội dung “$1”",
        "autosumm-newblank": "Đã tạo trang trống",
        "size-bytes": "$1 byte",
        "watchlistedit-clear-titles": "Các tiêu đề:",
        "watchlistedit-clear-submit": "Xóa sạch danh sách theo dõi (không thể lùi lại!)",
        "watchlistedit-clear-done": "Đã xóa sạch danh sách theo dõi của bạn.",
+       "watchlistedit-clear-jobqueue": "Danh sách theo dõi của bạn đang bị xoá. Quá trình này có thể tốn một khoảng thời gian!",
        "watchlistedit-clear-removed": "$1 tựa đề đã được xóa khỏi danh sách:",
        "watchlistedit-too-many": "Danh sách có quá nhiều trang để hiển thị.",
        "watchlisttools-clear": "Xóa sạch danh sách theo dõi",
        "tag-list-wrapper": "([[Special:Tags|{{PLURAL:$1}}Thẻ]]: $2)",
        "tag-mw-contentmodelchange": "thay đổi kiểu nội dung",
        "tag-mw-contentmodelchange-description": "Sửa đổi [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:ChangeContentModel thay đổi kiểu nội dung] của trang",
+       "tag-mw-new-redirect": "Trang đổi hướng mới",
+       "tag-mw-new-redirect-description": "Các sửa đổi tạo ra trang đổi hướng mới hoặc biến một trang thành trang đổi hướng.",
+       "tag-mw-removed-redirect": "Xoá đổi hướng",
+       "tag-mw-removed-redirect-description": "Các thay đổi biến một trang đổi hướng thành trang không đổi hướng",
+       "tag-mw-changed-redirect-target": "Thay đổi trang đích của đổi hướng",
+       "tag-mw-changed-redirect-target-description": "Các thay đổi làm thay đổi trang đích của một trang đổi hướng",
+       "tag-mw-blank": "Tẩy trống trang",
+       "tag-mw-blank-description": "Các sửa đổi tẩy trống (xoá trắng) một trang",
+       "tag-mw-replace": "Thay thế nội dung",
+       "tag-mw-replace-description": "Các sửa đổi thay đổi trên 90% nội dung của một trang",
+       "tag-mw-rollback": "Lùi tất cả",
+       "tag-mw-rollback-description": "Các thay đổi cho phép lùi hàng loạt thay đổi của một người dùng trước đó thông qua liên kết lùi tất cả",
+       "tag-mw-undo": "Lùi sửa",
+       "tag-mw-undo-description": "Các thay đổi lùi sửa (hoàn tác) những thay đổi trước đó thông qua liên kết lùi lại",
        "tags-title": "Thẻ đánh dấu",
        "tags-intro": "Trang này liệt kê các thẻ đánh dấu mà phần mềm dùng nó để đánh dấu một sửa đổi, và ý nghĩa của nó.",
        "tags-tag": "Tên thẻ",
index 478a77f..33ec285 100644 (file)
        "userjsyoucanpreview": "'''Mob:''' Välolös eli „{{int:showpreview}}“ ad blufön eli JS nulik olik bü dakip.",
        "usercsspreview": "'''Memolös, das anu te büologol eli CSS olik.'''\n'''No nog pedakipon!'''",
        "userjspreview": "'''Memolös, das anu te blufol/büologol eli JavaScript olik, no nog pedakipon!'''",
-       "userinvalidcssjstitle": "'''Nuned:''' No dabinon fomät: \"$1\".\nMemolös, das pads: .css e .js mutons labön tiädi minudik: {{ns:user}}:Foo/vector.css, no {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Nuned:''' No dabinon fomät: \"$1\".\nMemolös, das pads: .css e .js mutons labön tiädi minudik: {{ns:user}}:Foo/vector.css, no {{ns:user}}:Foo/Vector.css.",
        "updated": "(peatimükon)",
        "note": "'''Penet:'''",
        "previewnote": "'''Memolös, das is pajonon te büologed.'''\nVotükams olik no nog pedakipons!",
index 5caa187..4e53f27 100644 (file)
        "userjsyoucanpreview": "'''Nõvvoannõq:''' Pruugiq nuppi 'Näütäq proovikaehust' uma vahtsõ CCS-i vai JavaScripti ülekaemisõs, inne ku taa ärq pästät.",
        "usercsspreview": "'''Seo um CSS-i proovikaehus. Määntsitki muutuisi olõ-i viil pästet.'''",
        "userjspreview": "'''Unõhtagu-i, et seo kujo su umast javascriptist om viil pästmäldäq!'''",
-       "userinvalidcssjstitle": "'''Miildetulõtus:''' Olõ-i stiili nimega \"$1\". Piäq meelen, et pruukja säedüq .css- and .js-leheq piät nakkama väiku algustähega.",
+       "userinvalidconfigtitle": "'''Miildetulõtus:''' Olõ-i stiili nimega \"$1\". Piäq meelen, et pruukja säedüq .css- and .js-leheq piät nakkama väiku algustähega.",
        "updated": "(Värskis tett)",
        "note": "'''Miildetulõtus:'''",
        "previewnote": "'''Seo om õnnõ proovikaehus!''' \nSuq tettüq muutmisõq olõ-õi viil pästedüq!",
index c84e1e2..501d375 100644 (file)
        "userjsyoucanpreview": "'''Racsegne:''' eployîz l' boton «{{int:showpreview}}» po sayî vosse novea JavaScript divant del schaper.",
        "usercsspreview": "Èn rovyîz nén ki c' est djusse on prévoeyaedje di vosse stîle CSS d' uzeu.'''\n'''I n' a nén co stî schapé!'''",
        "userjspreview": "'''Èn rovyîz nén ki c' est djusse on prévoeyaedje/saye di vosse JavaScript d' uzeu, i n' a nén co stî schapé!'''",
-       "userinvalidcssjstitle": "'''Asteme:''' I n' a pont d' pea lomêye «$1». Tuzez ki les pådjes .css eyet .js des uzeus eployèt des tite e ptitès letes, metans {{ns:user}}:Toto/vector.css et nén {{ns:user}}:Toto/Vector.css.",
+       "userinvalidconfigtitle": "'''Asteme:''' I n' a pont d' pea lomêye «$1». Tuzez ki les pådjes .css eyet .js des uzeus eployèt des tite e ptitès letes, metans {{ns:user}}:Toto/vector.css et nén {{ns:user}}:Toto/Vector.css.",
        "updated": "(Ramidré)",
        "note": "'''Note :'''",
        "previewnote": "'''Èn rovyîz nén ki c' est djusse on prévoeyaedje.'''\n'''Les candmints n' ont nén co stî schapés!'''",
        "prefs-files": "Fitchîs",
        "prefs-custom-css": "CSS a vosse môde",
        "prefs-custom-js": "JavaScript a vosse môde",
-       "prefs-common-css-js": "CSS/JavaScript pårtaedjî po totes les peas:",
+       "prefs-common-config": "CSS/JavaScript pårtaedjî po totes les peas:",
        "prefs-reset-intro": "Vos ploz eployî ç' boton chal po rmete totes vos preferinces åzès prémetowès valixhances del waibe.\nÇoul n' pôrè nén esse disfwait.",
        "prefs-emailconfirm-label": "Acertinaedje di l' emile:",
        "youremail": "Vost emile:",
index 9c35d91..0cf2055 100644 (file)
        "prefs-files": "Mga paypay",
        "prefs-custom-css": "Custom CSS",
        "prefs-custom-js": "Custom JavaScript",
-       "prefs-common-css-js": "Saro nga CSS/JavaScript para han ngatanan nga mga panit:",
+       "prefs-common-config": "Saro nga CSS/JavaScript para han ngatanan nga mga panit:",
        "prefs-reset-intro": "Puydi nimo ini gamiton nga pakli para makareset han imo mga preperensya nga ginbutang nga daan han sityo. Diri ini puydi mapawaray-buhat.",
        "prefs-emailconfirm-label": "Kompirmasyon han email:",
        "youremail": "E-mail:",
index a82560f..9724fa7 100644 (file)
        "userjsyoucanpreview": "'''Xelal :''' di la digël nga cuq ci «Wonendi» ngir gis say xobi CSS walla JavaScript yu bees laata nga leen di denc.",
        "usercsspreview": "Bul fatte ne lii wonendib sa CSS rekk la; dencagoo say coppite!'''",
        "userjspreview": "'''Bul fatte ne lii ab wonendib sa yoonu javaScript rekk la; dencagoo say coppite!'''",
-       "userinvalidcssjstitle": "'''Moytul :''' amul genn col gu tudd « $1 ». Bul fatte ne xët yiy jeexee .css ak .js seeni koj ay araf yu tuut ñoo ciy tegu/.<br />ci misaal, {{ns:user}}:Foo/'''v'''ector.css moo baax, waaye bii du baax {{ns:user}}:Foo/'''V'''ector.css .",
+       "userinvalidconfigtitle": "'''Moytul :''' amul genn col gu tudd « $1 ». Bul fatte ne xët yiy jeexee .css ak .js seeni koj ay araf yu tuut ñoo ciy tegu/.<br />ci misaal, {{ns:user}}:Foo/'''v'''ector.css moo baax, waaye bii du baax {{ns:user}}:Foo/'''V'''ector.css .",
        "updated": "(bees na)",
        "note": "'''Karmat :'''",
        "previewnote": "'''Lii ab wonendi rekk la; coppite yi ci xët wi dencagoo leen!'''",
index b1040c4..a7f8844 100644 (file)
        "userjspreview": "'''注意侬只是垃许测试/预览侬个 JavaScript。'''\n'''还弗曾保存!'''",
        "sitecsspreview": "<strong>注意侬现在只是来上预览该CSS,还弗曾保存!</strong>",
        "sitejspreview": "<strong>注意侬现在只是来上预览该JavaScript代码,还弗曾保存!</strong>",
-       "userinvalidcssjstitle": "'''警告:''' 弗存在皮肤\"$1\"。注意自定义个 .css 搭 .js 页要使用小写标题,譬如,{{ns:user}}:Foo/vector.css 弗同于 {{ns:user}}:Foo/Vector.css。",
+       "userinvalidconfigtitle": "'''警告:''' 弗存在皮肤\"$1\"。注意自定义个 .css 搭 .js 页要使用小写标题,譬如,{{ns:user}}:Foo/vector.css 弗同于 {{ns:user}}:Foo/Vector.css。",
        "updated": "(已更新)",
        "note": "'''注意:'''",
        "previewnote": "<strong>该个还只是预览。</strong>倷个修改还朆保存!",
        "prefs-files": "文件",
        "prefs-custom-css": "自定义CSS",
        "prefs-custom-js": "自定义JavaScript",
-       "prefs-common-css-js": "所有皮肤一道用个CSS/JavaScript:",
+       "prefs-common-config": "所有皮肤一道用个CSS/JavaScript:",
        "prefs-emailconfirm-label": "电子邮件确认:",
        "youremail": "电子信箱:",
        "username": "{{GENDER:$1|用户名}}:",
index 47bb2a7..59410a6 100644 (file)
        "userjsyoucanpreview": "'''Селвг:''' тана шин JS боомг шүүҗ хадһлар, «{{int:showpreview}}» товч олзлтн.",
        "usercsspreview": "'''Тана CSS боомгин мел хәләвр бәәдг тускар тодлтн, тер ода чигн хадһлсн уга!'''",
        "userjspreview": "'''Тана JavaScript боомгин мел хәләвр бәәдг тускар тодлтн. Тана сольлһн ода чигн хадһлсн уга!'''",
-       "userinvalidcssjstitle": "'''Оньг өгтн:''' «$1» гидг нерәдлһтә хувцнь олҗ биш. Күүнә .css болн .js халхс һанцхн бичкн үзгүдтә бичсн кергтә, үлгүрнь «{{ns:user}}:Болвчн/vector.css»; «{{ns:user}}:Болвчн/Vector.css» - буру.",
+       "userinvalidconfigtitle": "'''Оньг өгтн:''' «$1» гидг нерәдлһтә хувцнь олҗ биш. Күүнә .css болн .js халхс һанцхн бичкн үзгүдтә бичсн кергтә, үлгүрнь «{{ns:user}}:Болвчн/vector.css»; «{{ns:user}}:Болвчн/Vector.css» - буру.",
        "updated": "(Шинрүлсн)",
        "note": "'''Аҗгллһн:'''",
        "previewnote": "'''Эн мел хәләвр бәәдг тускар тодлтн.'''\nТана сольлһн ода чигн хадһлсн уга!",
index eda5374..b296c6c 100644 (file)
        "prefs-files": "ფაილები",
        "prefs-custom-css": "მომხმარებლის CSS",
        "prefs-custom-js": "მომხმარებლის JS",
-       "prefs-common-css-js": "ზოგადი CSS/JS ყველა თემისთვის:",
+       "prefs-common-config": "ზოგადი CSS/JS ყველა თემისთვის:",
        "prefs-reset-intro": "ეს გვერდი შეიძლება გამოყენებული იქნეს თქვენი კონფიგურაციის შესაცვლელად საწყის კონფიგურაციაზე. ამ მოქმედების დადასტურების შემთხვევაში, თქვენ ვეღარ შეძლებთ მის გაუქმებას.",
        "prefs-emailconfirm-label": "ელ-ფოსტის დადასტურება:",
        "youremail": "ელ-ფოშტა:",
index d0d7069..429bd9a 100644 (file)
        "userjspreview": "'''געדענקט אַז איר טוט בלויז אויספרואוון\\פֿאראויסזען אייער באַניצער JavaScript.'''\n'''עס איז דערווײַל נאכנישט אָפגעהיטן!'''",
        "sitecsspreview": "'''געדענקט אַז איר טוט בלויז פֿאראויסזען דעם דאָזיקן CSS קאד.'''\n'''ער איז דערווײַל נאכנישט אויפֿגעהיטן!'''",
        "sitejspreview": "'''געדענקט אַז איר טוט בלויז פֿאראויסזען דעם דאָזיקן JavaScript קאד.'''\n'''ער איז דערווײַל נאכנישט אויפֿגעהיטן!'''",
-       "userinvalidcssjstitle": "'''ווארענונג:''' סאיז נישטא קיין סקין \"$1\". גדענקט אז קאסטעם .css און .js בלעטער נוצען לאוער קעיס טיטול, e.g. {{ns:user}}:Foo/vector.css ווי אנדערשט צו {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''ווארענונג:''' סאיז נישטא קיין סקין \"$1\". גדענקט אז קאסטעם .css און .js בלעטער נוצען לאוער קעיס טיטול, e.g. {{ns:user}}:Foo/vector.css ווי אנדערשט צו {{ns:user}}:Foo/Vector.css.",
        "updated": "(דערהיינטיגט)",
        "note": "'''באמערקונג:'''",
        "previewnote": "'''געדענקט אַז דאָס איז נאָר אַ פאָרויסקוק.'''\nאייערע ענדערונגען זענען נאָך נישט געהיט!",
        "prefs-files": "טעקעס",
        "prefs-custom-css": "באַניצער דעפֿינירט CSS",
        "prefs-custom-js": "באַניצער דעפֿינירט JS",
-       "prefs-common-css-js": "שותפֿותדיקער CSS/JS פֿאַר אַלע אויספֿארמירונגען:",
+       "prefs-common-config": "שותפֿותדיקער CSS/JS פֿאַר אַלע אויספֿארמירונגען:",
        "prefs-reset-intro": "איר קענט ניצן דעם בלאַט צוריקצושטעלן אײַערע פרעפֿערענצן גרונטלעך פֿאַרן ארט.\nמען קען דאָס נישט אַנולירן.",
        "prefs-emailconfirm-label": "ע-פאסט באַשטעטיקונג:",
        "youremail": "ע-פאסט:",
index 5183213..7dc88b6 100644 (file)
        "userjspreview": "''''Ẹ mọ́ gbàgbé pé àdánwò/àkọ́yẹ̀wò JavaScript oníṣe yín nìyí.'''\n'''Kò tíì jẹ́ mímúpamọ́!'''",
        "sitecsspreview": "'''Ẹ rántí pé àkọ́yẹ̀wò CSS nìyí.'''\n'''Kò tíì jẹ́ mímúpamọ!'''",
        "sitejspreview": "'''Ẹ rántí pé àkọ́yẹ̀wò àmìọ̀rọ̀ JavaScript nìyí.'''\n'''Kò tíì jẹ́ mímúpamọ!'''",
-       "userinvalidcssjstitle": "'''Ìkìlọ̀:''' Kò sí awọ-ìbojú \"$1\".\nẸ rántí pé àwọn ojúewé àkànṣe .css àti .js únlo àkọlé onílẹ́tà kékeré, f.a. {{ns:user}}:Foo/vector.css yàtò sí {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "'''Ìkìlọ̀:''' Kò sí awọ-ìbojú \"$1\".\nẸ rántí pé àwọn ojúewé àkànṣe .css àti .js únlo àkọlé onílẹ́tà kékeré, f.a. {{ns:user}}:Foo/vector.css yàtò sí {{ns:user}}:Foo/Vector.css.",
        "updated": "(Sísọdọ̀tun)",
        "note": "'''Àkíyèsí:'''",
        "previewnote": "'''Ẹ rántí pé àyẹ̀wò lásán nì yí.'''\nÀwọn àtúnṣe yín kò tíì jẹ́ kìkópamọ́!",
        "prefs-files": "Àwọn faili",
        "prefs-custom-css": "CSS àkànṣe",
        "prefs-custom-js": "JavaScript àkànṣe",
-       "prefs-common-css-js": "CSS/JavaScript àpínlò fún gbogbo àwọn awọ:",
+       "prefs-common-config": "CSS/JavaScript àpínlò fún gbogbo àwọn awọ:",
        "prefs-reset-intro": "Ẹ le lo ojúewé yìí láti ṣàtùntò àwọn ìfẹ́ràn yín sí àkọ́kọ́ṣe ibiìtakùn yìí.\nKò ní ṣeé dápadà mọ́.",
        "prefs-emailconfirm-label": "E-mail ìmúdájú:",
        "youremail": "E-mail:",
index 4b22b1b..0201c42 100644 (file)
        "userjspreview": "'''記住你而家只係測試/預覽緊你定義嘅JavaScript。'''\n'''佢嘅內容重未儲存!'''",
        "sitecsspreview": "'''記住你而家只係預覽呢段 CSS。'''\n'''佢嘅內容重未儲存!'''",
        "sitejspreview": "'''記住你而家只係預覽呢段 JavaScript 代碼。'''\n'''佢嘅內容重未儲存!'''",
-       "userinvalidcssjstitle": "'''警告:''' 無叫做 \"$1\" 嘅畫面。請記住自訂介面的 .css 和 .js 頁面時應使用細楷,例如:{{ns:user}}:Foo/vector.css 而唔係 {{ns:user}}:Foo/Vector.css 。",
+       "userinvalidconfigtitle": "'''警告:''' 無叫做 \"$1\" 嘅畫面。請記住自訂介面的 .css 和 .js 頁面時應使用細楷,例如:{{ns:user}}:Foo/vector.css 而唔係 {{ns:user}}:Foo/Vector.css 。",
        "updated": "(己更新)",
        "note": "'''留意:'''",
        "previewnote": "'''請記住呢個只係預覽。'''\n更改嘅内容重未儲存!",
        "prefs-files": "檔案",
        "prefs-custom-css": "自定 CSS",
        "prefs-custom-js": "自定 JavaScript",
-       "prefs-common-css-js": "共有嘅CSS同埋JavaScript畀所有畫面用:",
+       "prefs-common-config": "共有嘅CSS同埋JavaScript畀所有畫面用:",
        "prefs-reset-intro": "你可以用呢版去重設你嘅喜好設定到網站預設值。呢個動作無得番轉頭。",
        "prefs-emailconfirm-label": "電郵確認:",
        "youremail": "電郵:",
        "rcfilters-activefilters": "用緊嘅篩選條件",
        "rcfilters-advancedfilters": "進階嘅篩選條件",
        "rcfilters-limit-title": "顯示幾多結果",
+       "rcfilters-limit-and-date-label": "$1次{{PLURAL:$1|改動}},$2",
        "rcfilters-days-title": "最近幾多日",
        "rcfilters-hours-title": "最近幾多個鐘頭",
        "rcfilters-days-show-days": "$1 {{PLURAL:$1|日}}",
index 01a162d..f8c6f24 100644 (file)
        "userjsyoucanpreview": "'''Tip:''' Gebruuk de knoppe 'Naekieken' om je nieuwe JS te tessen voe da je opsli.",
        "usercsspreview": "'''Dit is alleên een voeôvertonieng van je persoônlijke CSS, dezen is nog nie opeslogen!'''",
        "userjspreview": "'''Let op: je test noe je persoônlijke JavaScript. De pagina is nie opeslogen!'''",
-       "userinvalidcssjstitle": "'''Waerschuwieng:''' der is hin skin \"$1\". Let op: jen eihen .css- en .js-pagina's behunnen mie een kleine letter, buvobbeld {{ns:user}}:Naem/vector.css in plekke van {{ns:user}}:Naem/Vector.css.",
+       "userinvalidconfigtitle": "'''Waerschuwieng:''' der is hin skin \"$1\". Let op: jen eihen .css- en .js-pagina's behunnen mie een kleine letter, buvobbeld {{ns:user}}:Naem/vector.css in plekke van {{ns:user}}:Naem/Vector.css.",
        "updated": "(Biehewerkt)",
        "note": "'''Opmerkieng:'''",
        "previewnote": "'''Let op: dit is een controlepagina; je tekst is nie opeslogen!'''",
index da7d0d2..96b9d91 100644 (file)
        "userjspreview": "<strong>请记住您现在只是在测试/预览您的用户JavaScript。它尚未保存!</strong>",
        "sitecsspreview": "<strong>请记住您现在只是在预览该CSS。它尚未保存!</strong>",
        "sitejspreview": "<strong>请记住您现在只是在预览该JavaScript代码。它尚未保存!</strong>",
-       "userinvalidcssjstitle": "<strong>警告:</strong>不存在皮肤“$1”。注意自定义的 .css 和 .js 页要使用小写标题,例如,{{ns:user}}:Foo/vector.css 不同于 {{ns:user}}:Foo/Vector.css。",
+       "userinvalidconfigtitle": "<strong>警告:</strong>不存在皮肤“$1”。注意自定义的 .css 和 .js 页要使用小写标题,例如,{{ns:user}}:Foo/vector.css 不同于 {{ns:user}}:Foo/Vector.css。",
        "updated": "(已更新)",
        "note": "<strong>注意:</strong>",
        "previewnote": "<strong>请记住这只是预览。</strong>您的更改尚未保存!",
        "prefs-files": "文件",
        "prefs-custom-css": "自定义CSS",
        "prefs-custom-js": "自定义JavaScript",
-       "prefs-common-css-js": "所有皮肤共用的CSS/JavaScript:",
+       "prefs-common-config": "所有皮肤共用的CSS/JavaScript:",
        "prefs-reset-intro": "可以通过本页面将系统设置重置为网站默认值。该操作无法撤销。",
        "prefs-emailconfirm-label": "电子邮件确认:",
        "youremail": "电子邮件:",
index 212487e..88e62cb 100644 (file)
        "userjspreview": "<strong>您目前正預覽您的使用者 JavaScript,JavaScript 還尚未儲存!</strong>",
        "sitecsspreview": "<strong>您目前正預覽此 CSS,CSS 還尚未儲存!</strong>",
        "sitejspreview": "<strong>您目前正預覽此 JavaScript,JavaScript 還尚未儲存!</strong>",
-       "userinvalidcssjstitle": "<strong>警告:</strong> 無此外觀樣式 \"$1\"。\n自訂的 .css 和 .js 頁面要使用小寫標題,例如:{{ns:user}}:Foo/vector.css 與 {{ns:user}}:Foo/Vector.css 是不同的。",
+       "userinvalidconfigtitle": "<strong>警告:</strong> 無此外觀樣式 \"$1\"。\n自訂的 .css 和 .js 頁面要使用小寫標題,例如:{{ns:user}}:Foo/vector.css 與 {{ns:user}}:Foo/Vector.css 是不同的。",
        "updated": "(已更新)",
        "note": "<strong>注意:</strong>",
        "previewnote": "<strong>您目前正在預覽,您的變更還尚未儲存!</strong>",
        "prevn-title": "前 $1 筆結果",
        "nextn-title": "後 $1 筆結果",
        "shown-title": "每頁顯示 $1 筆結果",
-       "viewprevnext": "檢視 ($1 {{int:pipe-separator}} $2) ($3)",
+       "viewprevnext": "檢視($1{{int:pipe-separator}} $2)($3)",
        "searchmenu-exists": "<strong>此 Wiki 已有名稱為 \"[[:$1]]\" 的頁面。</strong> {{PLURAL:$2|0=|或請參考其他搜尋結果。}}",
        "searchmenu-new": "<strong>於此 Wiki 建立頁面 \"[[:$1]]\"!</strong>{{PLURAL:$2|0=|或請參考您輸入的條件找到的搜尋結果。|或請參考其他搜尋結果。}}",
        "searchprofile-articles": "內容頁面",
        "prefs-files": "檔案",
        "prefs-custom-css": "自訂 CSS",
        "prefs-custom-js": "自訂 JavaScript",
-       "prefs-common-css-js": "所有外觀共用的 CSS/JavaScript:",
+       "prefs-common-config": "所有外觀共用的 CSS/JavaScript:",
        "prefs-reset-intro": "您可以使用此頁面重設您的偏好設定為網站預設值。\n這個動作將無法復原。",
        "prefs-emailconfirm-label": "電子郵件確認:",
        "youremail": "Email:",
        "thumbnail_dest_directory": "無法建立目標目錄",
        "thumbnail_image-type": "不支援的圖片類型",
        "thumbnail_gd-library": "未完成 GD 設定:缺少函數 $1",
+       "thumbnail_image-size-zero": "圖片檔案大小似乎為零。",
        "thumbnail_image-missing": "檔案遺失:$1",
        "thumbnail_image-failure-limit": "最近顯示此縮圖已發生太多次失敗 ($1 次或更多),請稍後再試。",
        "import": "匯入頁面",
diff --git a/maintenance/archives/patch-actor-table.sql b/maintenance/archives/patch-actor-table.sql
new file mode 100644 (file)
index 0000000..fdd95e8
--- /dev/null
@@ -0,0 +1,57 @@
+--
+-- patch-actor-table.sql
+--
+-- T167246. Add an `actor` table and various columns (and temporary tables) to reference it.
+
+CREATE TABLE /*_*/actor (
+  actor_id bigint unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  actor_user int unsigned,
+  actor_name varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/actor_user ON /*_*/actor (actor_user);
+CREATE UNIQUE INDEX /*i*/actor_name ON /*_*/actor (actor_name);
+
+CREATE TABLE /*_*/revision_actor_temp (
+  revactor_rev int unsigned NOT NULL,
+  revactor_actor bigint unsigned NOT NULL,
+  revactor_timestamp binary(14) NOT NULL default '',
+  revactor_page int unsigned NOT NULL,
+  PRIMARY KEY (revactor_rev, revactor_actor)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/revactor_rev ON /*_*/revision_actor_temp (revactor_rev);
+CREATE INDEX /*i*/actor_timestamp ON /*_*/revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX /*i*/page_actor_timestamp ON /*_*/revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
+ALTER TABLE /*_*/archive
+  ALTER COLUMN ar_user_text SET DEFAULT '',
+  ADD COLUMN ar_actor bigint unsigned NOT NULL DEFAULT 0 AFTER ar_user_text;
+CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
+
+ALTER TABLE /*_*/ipblocks
+  ADD COLUMN ipb_by_actor bigint unsigned NOT NULL DEFAULT 0 AFTER ipb_by_text;
+
+ALTER TABLE /*_*/image
+  ALTER COLUMN img_user_text SET DEFAULT '',
+  ADD COLUMN img_actor bigint unsigned NOT NULL DEFAULT 0 AFTER img_user_text;
+CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor, img_timestamp);
+
+ALTER TABLE /*_*/oldimage
+  ALTER COLUMN oi_user_text SET DEFAULT '',
+  ADD COLUMN oi_actor bigint unsigned NOT NULL DEFAULT 0 AFTER oi_user_text;
+CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
+
+ALTER TABLE /*_*/filearchive
+  ALTER COLUMN fa_user_text SET DEFAULT '',
+  ADD COLUMN fa_actor bigint unsigned NOT NULL DEFAULT 0 AFTER fa_user_text;
+CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
+
+ALTER TABLE /*_*/recentchanges
+  ALTER COLUMN rc_user_text SET DEFAULT '',
+  ADD COLUMN rc_actor bigint unsigned NOT NULL DEFAULT 0 AFTER rc_user_text;
+CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
+CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
+
+ALTER TABLE /*_*/logging
+  ADD COLUMN log_actor bigint unsigned NOT NULL DEFAULT 0 AFTER log_user_text;
+CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
+CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
index c52013b..326073f 100644 (file)
@@ -43,13 +43,19 @@ class DeleteDefaultMessages extends Maintenance {
 
                $this->output( "Checking existence of old default messages..." );
                $dbr = $this->getDB( DB_REPLICA );
-               $res = $dbr->select( [ 'page', 'revision' ],
+
+               $actorQuery = ActorMigration::newMigration()
+                       ->getWhere( $dbr, 'rev_user', User::newFromName( 'MediaWiki default' ) );
+               $res = $dbr->select(
+                       [ 'page', 'revision' ] + $actorQuery['tables'],
                        [ 'page_namespace', 'page_title' ],
                        [
                                'page_namespace' => NS_MEDIAWIKI,
-                               'page_latest=rev_id',
-                               'rev_user_text' => 'MediaWiki default',
-                       ]
+                               $actorQuery['conds'],
+                       ],
+                       __METHOD__,
+                       [],
+                       [ 'revision' => [ 'JOIN', 'page_latest=rev_id' ] ] + $actorQuery['joins']
                );
 
                if ( $dbr->numRows( $res ) == 0 ) {
index 57fd91b..e1b1682 100644 (file)
@@ -59,11 +59,15 @@ class FixUserRegistration extends Maintenance {
                                $id = $row->user_id;
                                $lastId = $id;
                                // Get first edit time
+                               $actorQuery = ActorMigration::newMigration()
+                                       ->getWhere( $dbw, 'rev_user', User::newFromId( $id ) );
                                $timestamp = $dbw->selectField(
-                                       'revision',
+                                       [ 'revision' ] + $actorQuery['tables'],
                                        'MIN(rev_timestamp)',
-                                       [ 'rev_user' => $id ],
-                                       __METHOD__
+                                       $actorQuery['conds'],
+                                       __METHOD__,
+                                       [],
+                                       $actorQuery['joins']
                                );
                                // Update
                                if ( $timestamp !== null ) {
index 3c4336f..f7ef7a2 100644 (file)
@@ -38,9 +38,9 @@ in the load balancer, usually indicating a replication environment.' );
        }
 
        public function execute() {
+               global $wgActorTableSchemaMigrationStage;
+
                $dbw = $this->getDB( DB_MASTER );
-               $user = $dbw->tableName( 'user' );
-               $revision = $dbw->tableName( 'revision' );
 
                // Autodetect mode...
                if ( $this->hasOption( 'background' ) ) {
@@ -51,6 +51,17 @@ in the load balancer, usually indicating a replication environment.' );
                        $backgroundMode = wfGetLB()->getServerCount() > 1;
                }
 
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
+
+               $needSpecialQuery = ( $wgActorTableSchemaMigrationStage !== MIGRATION_OLD &&
+                       $wgActorTableSchemaMigrationStage !== MIGRATION_NEW );
+               if ( $needSpecialQuery ) {
+                       foreach ( $actorQuery['joins'] as &$j ) {
+                               $j[0] = 'JOIN'; // replace LEFT JOIN
+                       }
+                       unset( $j );
+               }
+
                if ( $backgroundMode ) {
                        $this->output( "Using replication-friendly background mode...\n" );
 
@@ -62,15 +73,55 @@ in the load balancer, usually indicating a replication environment.' );
                        $migrated = 0;
                        for ( $min = 0; $min <= $lastUser; $min += $chunkSize ) {
                                $max = $min + $chunkSize;
-                               $result = $dbr->query(
-                                       "SELECT
-                                               user_id,
-                                               COUNT(rev_user) AS user_editcount
-                                       FROM $user
-                                       LEFT OUTER JOIN $revision ON user_id=rev_user
-                                       WHERE user_id > $min AND user_id <= $max
-                                       GROUP BY user_id",
-                                       __METHOD__ );
+
+                               if ( $needSpecialQuery ) {
+                                       // Use separate subqueries to collect counts with the old
+                                       // and new schemas, to avoid having to do whole-table scans.
+                                       $result = $dbr->select(
+                                               [
+                                                       'user',
+                                                       'rev1' => '('
+                                                               . $dbr->selectSQLText(
+                                                                       [ 'revision', 'revision_actor_temp' ],
+                                                                       [ 'rev_user', 'ct' => 'COUNT(*)' ],
+                                                                       [
+                                                                               "rev_user > $min AND rev_user <= $max",
+                                                                               'revactor_rev' => null,
+                                                                       ],
+                                                                       __METHOD__,
+                                                                       [ 'GROUP BY' => 'rev_user' ],
+                                                                       [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
+                                                               ) . ')',
+                                                       'rev2' => '('
+                                                               . $dbr->selectSQLText(
+                                                                       [ 'revision' ] + $actorQuery['tables'],
+                                                                       [ 'actor_user', 'ct' => 'COUNT(*)' ],
+                                                                       "actor_user > $min AND actor_user <= $max",
+                                                                       __METHOD__,
+                                                                       [ 'GROUP BY' => 'actor_user' ],
+                                                                       $actorQuery['joins']
+                                                               ) . ')',
+                                               ],
+                                               [ 'user_id', 'user_editcount' => 'COALESCE(rev1.ct,0) + COALESCE(rev2.ct,0)' ],
+                                               "user_id > $min AND user_id <= $max",
+                                               __METHOD__,
+                                               [],
+                                               [
+                                                       'rev1' => [ 'LEFT JOIN', 'user_id = rev_user' ],
+                                                       'rev2' => [ 'LEFT JOIN', 'user_id = actor_user' ],
+                                               ]
+                                       );
+                               } else {
+                                       $revUser = $actorQuery['fields']['rev_user'];
+                                       $result = $dbr->select(
+                                               [ 'user', 'rev' => [ 'revision' ] + $actorQuery['tables'] ],
+                                               [ 'user_id', 'user_editcount' => "COUNT($revUser)" ],
+                                               "user_id > $min AND user_id <= $max",
+                                               __METHOD__,
+                                               [ 'GROUP BY' => 'user_id' ],
+                                               [ 'rev' => [ 'LEFT JOIN', "user_id = $revUser" ] ] + $actorQuery['joins']
+                                       );
+                               }
 
                                foreach ( $result as $row ) {
                                        $dbw->update( 'user',
@@ -93,8 +144,43 @@ in the load balancer, usually indicating a replication environment.' );
                        }
                } else {
                        $this->output( "Using single-query mode...\n" );
-                       $sql = "UPDATE $user SET user_editcount=(SELECT COUNT(*) FROM $revision WHERE rev_user=user_id)";
-                       $dbw->query( $sql );
+
+                       $user = $dbw->tableName( 'user' );
+                       if ( $needSpecialQuery ) {
+                               $subquery1 = $dbw->selectSQLText(
+                                       [ 'revision', 'revision_actor_temp' ],
+                                       [ 'COUNT(*)' ],
+                                       [
+                                               'user_id = rev_user',
+                                               'revactor_rev' => null,
+                                       ],
+                                       __METHOD__,
+                                       [],
+                                       [ 'revision_actor_temp' => [ 'LEFT JOIN', 'revactor_rev = rev_id' ] ]
+                               );
+                               $subquery2 = $dbw->selectSQLText(
+                                       [ 'revision' ] + $actorQuery['tables'],
+                                       [ 'COUNT(*)' ],
+                                       'user_id = actor_user',
+                                       __METHOD__,
+                                       [],
+                                       $actorQuery['joins']
+                               );
+                               $dbw->query(
+                                       "UPDATE $user SET user_editcount=($subquery1) + ($subquery2)",
+                                       __METHOD__
+                               );
+                       } else {
+                               $subquery = $dbw->selectSQLText(
+                                       [ 'revision' ] + $actorQuery['tables'],
+                                       [ 'COUNT(*)' ],
+                                       [ 'user_id = ' . $actorQuery['fields']['rev_user'] ],
+                                       __METHOD__,
+                                       [],
+                                       $actorQuery['joins']
+                               );
+                               $dbw->query( "UPDATE $user SET user_editcount=($subquery)", __METHOD__ );
+                       }
                }
 
                $this->output( "Done!\n" );
diff --git a/maintenance/migrateActors.php b/maintenance/migrateActors.php
new file mode 100644 (file)
index 0000000..1657f14
--- /dev/null
@@ -0,0 +1,551 @@
+<?php
+/**
+ * Migrate actors from pre-1.31 columns to the 'actor' table
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup Maintenance
+ */
+
+use Wikimedia\Rdbms\IDatabase;
+
+require_once __DIR__ . '/Maintenance.php';
+
+/**
+ * Maintenance script that migrates actors from pre-1.31 columns to the
+ * 'actor' table
+ *
+ * @ingroup Maintenance
+ */
+class MigrateActors extends LoggedUpdateMaintenance {
+       public function __construct() {
+               parent::__construct();
+               $this->addDescription( 'Migrates actors from pre-1.31 columns to the \'actor\' table' );
+               $this->setBatchSize( 100 );
+       }
+
+       protected function getUpdateKey() {
+               return __CLASS__;
+       }
+
+       protected function doDBUpdates() {
+               global $wgActorTableSchemaMigrationStage;
+
+               if ( $wgActorTableSchemaMigrationStage < MIGRATION_WRITE_NEW ) {
+                       $this->output(
+                               "...cannot update while \$wgActorTableSchemaMigrationStage < MIGRATION_WRITE_NEW\n"
+                       );
+                       return false;
+               }
+
+               $this->output( "Creating actor entries for all registered users\n" );
+               $end = 0;
+               $dbw = $this->getDB( DB_MASTER );
+               $max = $dbw->selectField( 'user', 'MAX(user_id)', false, __METHOD__ );
+               $count = 0;
+               while ( $end < $max ) {
+                       $start = $end + 1;
+                       $end = min( $start + $this->mBatchSize, $max );
+                       $this->output( "... $start - $end\n" );
+                       $dbw->insertSelect(
+                               'actor',
+                               'user',
+                               [ 'actor_user' => 'user_id', 'actor_name' => 'user_name' ],
+                               [ "user_id >= $start", "user_id <= $end" ],
+                               __METHOD__,
+                               [ 'IGNORE' ],
+                               [ 'ORDER BY' => [ 'user_id' ] ]
+                       );
+                       $count += $dbw->affectedRows();
+                       wfWaitForSlaves();
+               }
+               $this->output( "Completed actor creation, added $count new actor(s)\n" );
+
+               $errors = 0;
+               $errors += $this->migrateToTemp(
+                       'revision', 'rev_id', [ 'revactor_timestamp' => 'rev_timestamp', 'revactor_page' => 'rev_page' ],
+                       'rev_user', 'rev_user_text', 'revactor_rev', 'revactor_actor'
+               );
+               $errors += $this->migrate( 'archive', 'ar_id', 'ar_user', 'ar_user_text', 'ar_actor' );
+               $errors += $this->migrate( 'ipblocks', 'ipb_id', 'ipb_by', 'ipb_by_text', 'ipb_by_actor' );
+               $errors += $this->migrate( 'image', 'img_name', 'img_user', 'img_user_text', 'img_actor' );
+               $errors += $this->migrate(
+                       'oldimage', [ 'oi_name', 'oi_timestamp' ], 'oi_user', 'oi_user_text', 'oi_actor'
+               );
+               $errors += $this->migrate( 'filearchive', 'fa_id', 'fa_user', 'fa_user_text', 'fa_actor' );
+               $errors += $this->migrate( 'recentchanges', 'rc_id', 'rc_user', 'rc_user_text', 'rc_actor' );
+               $errors += $this->migrate( 'logging', 'log_id', 'log_user', 'log_user_text', 'log_actor' );
+
+               $errors += $this->migrateLogSearch();
+
+               return $errors === 0;
+       }
+
+       /**
+        * Calculate a "next" condition and a display string
+        * @param IDatabase $dbw
+        * @param string[] $primaryKey Primary key of the table.
+        * @param object $row Database row
+        * @return array [ string $next, string $display ]
+        */
+       private function makeNextCond( $dbw, $primaryKey, $row ) {
+               $next = '';
+               $display = [];
+               for ( $i = count( $primaryKey ) - 1; $i >= 0; $i-- ) {
+                       $field = $primaryKey[$i];
+                       $display[] = $field . '=' . $row->$field;
+                       $value = $dbw->addQuotes( $row->$field );
+                       if ( $next === '' ) {
+                               $next = "$field > $value";
+                       } else {
+                               $next = "$field > $value OR $field = $value AND ($next)";
+                       }
+               }
+               $display = implode( ' ', array_reverse( $display ) );
+               return [ $next, $display ];
+       }
+
+       /**
+        * Add actors for anons in a set of rows
+        * @param IDatabase $dbw
+        * @param string $nameField
+        * @param object[] &$rows
+        * @param array &$complainedAboutUsers
+        * @param int &$countErrors
+        * @return int Count of actors inserted
+        */
+       private function addActorsForRows(
+               IDatabase $dbw, $nameField, array &$rows, array &$complainedAboutUsers, &$countErrors
+       ) {
+               $needActors = [];
+               $countActors = 0;
+
+               $keep = [];
+               foreach ( $rows as $index => $row ) {
+                       $keep[$index] = true;
+                       if ( $row->actor_id === null ) {
+                               // All registered users should have an actor_id already. So
+                               // if we have a usable name here, it means they didn't run
+                               // maintenance/cleanupUsersWithNoId.php
+                               $name = $row->$nameField;
+                               if ( User::isUsableName( $name ) ) {
+                                       if ( !isset( $complainedAboutUsers[$name] ) ) {
+                                               $complainedAboutUsers[$name] = true;
+                                               $this->error(
+                                                       "User name \"$name\" is usable, cannot create an anonymous actor for it."
+                                                       . " Run maintenance/cleanupUsersWithNoId.php to fix this situation.\n"
+                                               );
+                                       }
+                                       unset( $keep[$index] );
+                                       $countErrors++;
+                               } else {
+                                       $needActors[$name] = 0;
+                               }
+                       }
+               }
+               $rows = array_intersect_key( $rows, $keep );
+
+               if ( $needActors ) {
+                       $dbw->insert(
+                               'actor',
+                               array_map( function ( $v ) {
+                                       return [
+                                               'actor_name' => $v,
+                                       ];
+                               }, array_keys( $needActors ) ),
+                               __METHOD__
+                       );
+                       $countActors += $dbw->affectedRows();
+
+                       $res = $dbw->select(
+                               'actor',
+                               [ 'actor_id', 'actor_name' ],
+                               [ 'actor_name' => array_keys( $needActors ) ],
+                               __METHOD__
+                       );
+                       foreach ( $res as $row ) {
+                               $needActors[$row->actor_name] = $row->actor_id;
+                       }
+                       foreach ( $rows as $row ) {
+                               if ( $row->actor_id === null ) {
+                                       $row->actor_id = $needActors[$row->$nameField];
+                               }
+                       }
+               }
+
+               return $countActors;
+       }
+
+       /**
+        * Migrate actors in a table.
+        *
+        * Assumes any row with the actor field non-zero have already been migrated.
+        * Blanks the name field when migrating.
+        *
+        * @param string $table Table to migrate
+        * @param string|string[] $primaryKey Primary key of the table.
+        * @param string $userField User ID field name
+        * @param string $nameField User name field name
+        * @param string $actorField Actor field name
+        * @return int Number of errors
+        */
+       protected function migrate( $table, $primaryKey, $userField, $nameField, $actorField ) {
+               $complainedAboutUsers = [];
+
+               $primaryKey = (array)$primaryKey;
+               $pkFilter = array_flip( $primaryKey );
+               $this->output(
+                       "Beginning migration of $table.$userField and $table.$nameField to $table.$actorField\n"
+               );
+               wfWaitForSlaves();
+
+               $dbw = $this->getDB( DB_MASTER );
+               $next = '1=1';
+               $countUpdated = 0;
+               $countActors = 0;
+               $countErrors = 0;
+               while ( true ) {
+                       // Fetch the rows needing update
+                       $res = $dbw->select(
+                               [ $table, 'actor' ],
+                               array_merge( $primaryKey, [ $userField, $nameField, 'actor_id' ] ),
+                               [
+                                       $actorField => 0,
+                                       $next,
+                               ],
+                               __METHOD__,
+                               [
+                                       'ORDER BY' => $primaryKey,
+                                       'LIMIT' => $this->mBatchSize,
+                               ],
+                               [
+                                       'actor' => [
+                                               'LEFT JOIN',
+                                               "$userField != 0 AND actor_user = $userField OR "
+                                               . "($userField = 0 OR $userField IS NULL) AND actor_name = $nameField"
+                                       ]
+                               ]
+                       );
+                       if ( !$res->numRows() ) {
+                               break;
+                       }
+
+                       // Insert new actors for rows that need one
+                       $rows = iterator_to_array( $res );
+                       $lastRow = end( $rows );
+                       $countActors += $this->addActorsForRows(
+                               $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
+                       );
+
+                       // Update the existing rows
+                       foreach ( $rows as $row ) {
+                               if ( !$row->actor_id ) {
+                                       list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
+                                       $this->error(
+                                               "Could not make actor for row with $display "
+                                               . "$userField={$row->$userField} $nameField={$row->$nameField}\n"
+                                       );
+                                       $countErrors++;
+                                       continue;
+                               }
+                               $dbw->update(
+                                       $table,
+                                       [
+                                               $actorField => $row->actor_id,
+                                               $nameField => '',
+                                       ],
+                                       array_intersect_key( (array)$row, $pkFilter ) + [
+                                               $actorField => 0
+                                       ],
+                                       __METHOD__
+                               );
+                               $countUpdated += $dbw->affectedRows();
+                       }
+
+                       list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
+                       $this->output( "... $display\n" );
+                       wfWaitForSlaves();
+               }
+
+               $this->output(
+                       "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
+                       . "$countErrors error(s)\n"
+               );
+               return $countErrors;
+       }
+
+       /**
+        * Migrate actors in a table to a temporary table.
+        *
+        * Assumes the new table is named "{$table}_actor_temp", and it has two
+        * columns, in order, being the primary key of the original table and the
+        * actor ID field.
+        * Blanks the name field when migrating.
+        *
+        * @param string $table Table to migrate
+        * @param string $primaryKey Primary key of the table.
+        * @param array $extra Extra fields to copy
+        * @param string $userField User ID field name
+        * @param string $nameField User name field name
+        * @param string $newPrimaryKey Primary key of the new table.
+        * @param string $actorField Actor field name
+        */
+       protected function migrateToTemp(
+               $table, $primaryKey, $extra, $userField, $nameField, $newPrimaryKey, $actorField
+       ) {
+               $complainedAboutUsers = [];
+
+               $newTable = $table . '_actor_temp';
+               $this->output(
+                       "Beginning migration of $table.$userField and $table.$nameField to $newTable.$actorField\n"
+               );
+               wfWaitForSlaves();
+
+               $dbw = $this->getDB( DB_MASTER );
+               $next = [];
+               $countUpdated = 0;
+               $countActors = 0;
+               $countErrors = 0;
+               while ( true ) {
+                       // Fetch the rows needing update
+                       $res = $dbw->select(
+                               [ $table, $newTable, 'actor' ],
+                               [ $primaryKey, $userField, $nameField, 'actor_id' ] + $extra,
+                               [ $newPrimaryKey => null ] + $next,
+                               __METHOD__,
+                               [
+                                       'ORDER BY' => $primaryKey,
+                                       'LIMIT' => $this->mBatchSize,
+                               ],
+                               [
+                                       $newTable => [ 'LEFT JOIN', "{$primaryKey}={$newPrimaryKey}" ],
+                                       'actor' => [
+                                               'LEFT JOIN',
+                                               "$userField != 0 AND actor_user = $userField OR "
+                                               . "($userField = 0 OR $userField IS NULL) AND actor_name = $nameField"
+                                       ]
+                               ]
+                       );
+                       if ( !$res->numRows() ) {
+                               break;
+                       }
+
+                       // Insert new actors for rows that need one
+                       $rows = iterator_to_array( $res );
+                       $lastRow = end( $rows );
+                       $countActors += $this->addActorsForRows(
+                               $dbw, $nameField, $rows, $complainedAboutUsers, $countErrors
+                       );
+
+                       // Update rows
+                       if ( $rows ) {
+                               $inserts = [];
+                               $updates = [];
+                               foreach ( $rows as $row ) {
+                                       if ( !$row->actor_id ) {
+                                               list( , $display ) = $this->makeNextCond( $dbw, [ $primaryKey ], $row );
+                                               $this->error(
+                                                       "Could not make actor for row with $display "
+                                                       . "$userField={$row->$userField} $nameField={$row->$nameField}\n"
+                                               );
+                                               $countErrors++;
+                                               continue;
+                                       }
+                                       $ins = [
+                                               $newPrimaryKey => $row->$primaryKey,
+                                               $actorField => $row->actor_id,
+                                       ];
+                                       foreach ( $extra as $to => $from ) {
+                                               $ins[$to] = $row->$to; // It's aliased
+                                       }
+                                       $inserts[] = $ins;
+                                       $updates[] = $row->$primaryKey;
+                               }
+                               $this->beginTransaction( $dbw, __METHOD__ );
+                               $dbw->insert( $newTable, $inserts, __METHOD__ );
+                               $dbw->update( $table, [ $nameField => '' ], [ $primaryKey => $updates ], __METHOD__ );
+                               $countUpdated += $dbw->affectedRows();
+                               $this->commitTransaction( $dbw, __METHOD__ );
+                       }
+
+                       // Calculate the "next" condition
+                       list( $n, $display ) = $this->makeNextCond( $dbw, [ $primaryKey ], $lastRow );
+                       $next = [ $n ];
+                       $this->output( "... $display\n" );
+                       wfWaitForSlaves();
+               }
+
+               $this->output(
+                       "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
+                       . "$countErrors error(s)\n"
+               );
+               return $countErrors;
+       }
+
+       /**
+        * Migrate actors in the log_search table.
+        * @return int Number of errors
+        */
+       protected function migrateLogSearch() {
+               $complainedAboutUsers = [];
+
+               $primaryKey = [ 'ls_field', 'ls_value' ];
+               $pkFilter = array_flip( $primaryKey );
+               $this->output( "Beginning migration of log_search\n" );
+               wfWaitForSlaves();
+
+               $dbw = $this->getDB( DB_MASTER );
+               $countUpdated = 0;
+               $countActors = 0;
+               $countErrors = 0;
+
+               $next = '1=1';
+               while ( true ) {
+                       // Fetch the rows needing update
+                       $res = $dbw->select(
+                               [ 'log_search', 'actor' ],
+                               [ 'ls_field', 'ls_value', 'actor_id' ],
+                               [
+                                       'ls_field' => 'target_author_id',
+                                       $next,
+                               ],
+                               __METHOD__,
+                               [
+                                       'DISTINCT',
+                                       'ORDER BY' => [ 'ls_value' ],
+                                       'LIMIT' => $this->mBatchSize,
+                               ],
+                               [ 'actor' => [ 'LEFT JOIN', 'ls_value = ' . $dbw->buildStringCast( 'actor_user' ) ] ]
+                       );
+                       if ( !$res->numRows() ) {
+                               break;
+                       }
+
+                       // Update the rows
+                       $del = [];
+                       foreach ( $res as $row ) {
+                               $lastRow = $row;
+                               if ( !$row->actor_id ) {
+                                       list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
+                                       $this->error( "No actor for row with $display\n" );
+                                       $countErrors++;
+                                       continue;
+                               }
+                               $dbw->update(
+                                       'log_search',
+                                       [
+                                               'ls_field' => 'target_author_actor',
+                                               'ls_value' => $row->actor_id,
+                                       ],
+                                       [
+                                               'ls_field' => $row->ls_field,
+                                               'ls_value' => $row->ls_value,
+                                       ],
+                                       __METHOD__,
+                                       [ 'IGNORE' ]
+                               );
+                               $countUpdated += $dbw->affectedRows();
+                               $del[] = $row->ls_value;
+                       }
+                       if ( $del ) {
+                               $dbw->delete(
+                                       'log_search', [ 'ls_field' => 'target_author_id', 'ls_value' => $del ], __METHOD__
+                               );
+                               $countUpdated += $dbw->affectedRows();
+                       }
+
+                       list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
+                       $this->output( "... $display\n" );
+                       wfWaitForSlaves();
+               }
+
+               $next = '1=1';
+               while ( true ) {
+                       // Fetch the rows needing update
+                       $res = $dbw->select(
+                               [ 'log_search', 'actor' ],
+                               [ 'ls_field', 'ls_value', 'actor_id' ],
+                               [
+                                       'ls_field' => 'target_author_ip',
+                                       $next,
+                               ],
+                               __METHOD__,
+                               [
+                                       'DISTINCT',
+                                       'ORDER BY' => [ 'ls_value' ],
+                                       'LIMIT' => $this->mBatchSize,
+                               ],
+                               [ 'actor' => [ 'LEFT JOIN', 'ls_value = actor_name' ] ]
+                       );
+                       if ( !$res->numRows() ) {
+                               break;
+                       }
+
+                       // Insert new actors for rows that need one
+                       $rows = iterator_to_array( $res );
+                       $lastRow = end( $rows );
+                       $countActors += $this->addActorsForRows(
+                               $dbw, 'ls_value', $rows, $complainedAboutUsers, $countErrors
+                       );
+
+                       // Update the rows
+                       $del = [];
+                       foreach ( $rows as $row ) {
+                               if ( !$row->actor_id ) {
+                                       list( , $display ) = $this->makeNextCond( $dbw, $primaryKey, $row );
+                                       $this->error( "Could not make actor for row with $display\n" );
+                                       $countErrors++;
+                                       continue;
+                               }
+                               $dbw->update(
+                                       'log_search',
+                                       [
+                                               'ls_field' => 'target_author_actor',
+                                               'ls_value' => $row->actor_id,
+                                       ],
+                                       [
+                                               'ls_field' => $row->ls_field,
+                                               'ls_value' => $row->ls_value,
+                                       ],
+                                       __METHOD__,
+                                       [ 'IGNORE' ]
+                               );
+                               $countUpdated += $dbw->affectedRows();
+                               $del[] = $row->ls_value;
+                       }
+                       if ( $del ) {
+                               $dbw->delete(
+                                       'log_search', [ 'ls_field' => 'target_author_ip', 'ls_value' => $del ], __METHOD__
+                               );
+                               $countUpdated += $dbw->affectedRows();
+                       }
+
+                       list( $next, $display ) = $this->makeNextCond( $dbw, $primaryKey, $lastRow );
+                       $this->output( "... $display\n" );
+                       wfWaitForSlaves();
+               }
+
+               $this->output(
+                       "Completed migration, updated $countUpdated row(s) with $countActors new actor(s), "
+                       . "$countErrors error(s)\n"
+               );
+               return $countErrors;
+       }
+}
+
+$maintClass = "MigrateActors";
+require_once RUN_MAINTENANCE_IF_MAIN;
diff --git a/maintenance/mssql/archives/patch-actor-table.sql b/maintenance/mssql/archives/patch-actor-table.sql
new file mode 100644 (file)
index 0000000..b26ad44
--- /dev/null
@@ -0,0 +1,53 @@
+--
+-- patch-actor-table.sql
+--
+-- T167246. Add an `actor` table and various columns (and temporary tables) to reference it.
+
+CREATE TABLE /*_*/actor (
+  actor_id bigint unsigned NOT NULL CONSTRAINT PK_actor PRIMARY KEY IDENTITY(0,1),
+  actor_user int unsigned,
+  actor_name nvarchar(255) NOT NULL
+);
+CREATE UNIQUE INDEX /*i*/actor_user ON /*_*/actor (actor_user);
+CREATE UNIQUE INDEX /*i*/actor_name ON /*_*/actor (actor_name);
+
+-- Dummy
+INSERT INTO /*_*/actor (actor_name) VALUES ('##Anonymous##');
+
+CREATE TABLE /*_*/revision_actor_temp (
+  revactor_rev int unsigned NOT NULL CONSTRAINT FK_revactor_rev FOREIGN KEY REFERENCES /*_*/revision(rev_id) ON DELETE CASCADE,
+  revactor_actor bigint unsigned NOT NULL,
+  revactor_timestamp varchar(14) NOT NULL CONSTRAINT DF_revactor_timestamp DEFAULT '',
+  revactor_page int unsigned NOT NULL,
+  CONSTRAINT PK_revision_actor_temp PRIMARY KEY (revactor_rev, revactor_actor)
+);
+CREATE UNIQUE INDEX /*i*/revactor_rev ON /*_*/revision_actor_temp (revactor_rev);
+CREATE INDEX /*i*/actor_timestamp ON /*_*/revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX /*i*/page_actor_timestamp ON /*_*/revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
+ALTER TABLE /*_*/archive ADD CONSTRAINT DF_ar_user_text DEFAULT '' FOR ar_user_text;
+ALTER TABLE /*_*/archive ADD ar_actor bigint unsigned NOT NULL CONSTRAINT DF_ar_actor DEFAULT 0;
+CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
+
+ALTER TABLE /*_*/ipblocks ADD ipb_by_actor bigint unsigned NOT NULL CONSTRAINT DF_ipb_by_actor DEFAULT 0;
+
+ALTER TABLE /*_*/image ADD CONSTRAINT DF_img_user_text DEFAULT '' FOR img_user_text;
+ALTER TABLE /*_*/image ADD img_actor bigint unsigned NOT NULL CONSTRAINT DF_img_actor DEFAULT 0;
+CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor, img_timestamp);
+
+ALTER TABLE /*_*/oldimage ADD CONSTRAINT DF_oi_user_text DEFAULT '' FOR oi_user_text;
+ALTER TABLE /*_*/oldimage ADD oi_actor bigint unsigned NOT NULL CONSTRAINT DF_oi_actor DEFAULT 0;
+CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
+
+ALTER TABLE /*_*/filearchive ADD CONSTRAINT DF_fa_user_text DEFAULT '' FOR fa_user_text;
+ALTER TABLE /*_*/filearchive ADD fa_actor bigint unsigned NOT NULL CONSTRAINT DF_fa_actor DEFAULT 0;
+CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
+
+ALTER TABLE /*_*/recentchanges ADD CONSTRAINT DF_rc_user_text DEFAULT '' FOR rc_user_text;
+ALTER TABLE /*_*/recentchanges ADD rc_actor bigint unsigned NOT NULL CONSTRAINT DF_rc_actor DEFAULT 0;
+CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
+CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
+
+ALTER TABLE /*_*/logging ADD log_actor bigint unsigned NOT NULL CONSTRAINT DF_log_actor DEFAULT 0;
+CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
+CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
index 4673264..5348c47 100644 (file)
@@ -54,6 +54,23 @@ CREATE INDEX /*i*/user_email ON /*_*/mwuser (user_email);
 -- Insert a dummy user to represent anons
 INSERT INTO /*_*/mwuser (user_name) VALUES ('##Anonymous##');
 
+--
+-- The "actor" table associates user names or IP addresses with integers for
+-- the benefit of other tables that need to refer to either logged-in or
+-- logged-out users. If something can only ever be done by logged-in users, it
+-- can refer to the user table directly.
+--
+CREATE TABLE /*_*/actor (
+  actor_id bigint unsigned NOT NULL CONSTRAINT PK_actor PRIMARY KEY IDENTITY(0,1),
+  actor_user int unsigned,
+  actor_name nvarchar(255) NOT NULL
+);
+CREATE UNIQUE INDEX /*i*/actor_user ON /*_*/actor (actor_user);
+CREATE UNIQUE INDEX /*i*/actor_name ON /*_*/actor (actor_name);
+
+-- Insert a dummy actor to represent no actor
+INSERT INTO /*_*/actor (actor_name) VALUES ('##Anonymous##');
+
 --
 -- User permissions have been broken out to a separate table;
 -- this allows sites with a shared user table to have different
@@ -200,7 +217,7 @@ INSERT INTO /*_*/revision (rev_page,rev_text_id,rev_comment,rev_user,rev_len) VA
 ALTER TABLE /*_*/page ADD CONSTRAINT FK_page_latest_page_id FOREIGN KEY (page_latest) REFERENCES /*_*/revision(rev_id);
 
 --
--- Temporary table to avoid blocking on an alter of revision.
+-- Temporary tables to avoid blocking on an alter of revision.
 --
 -- On large wikis like the English Wikipedia, altering the revision table is a
 -- months-long process. This table is being created to avoid such an alter, and
@@ -213,6 +230,17 @@ CREATE TABLE /*_*/revision_comment_temp (
 );
 CREATE UNIQUE INDEX /*i*/revcomment_rev ON /*_*/revision_comment_temp (revcomment_rev);
 
+CREATE TABLE /*_*/revision_actor_temp (
+  revactor_rev int unsigned NOT NULL CONSTRAINT FK_revactor_rev FOREIGN KEY REFERENCES /*_*/revision(rev_id) ON DELETE CASCADE,
+  revactor_actor bigint unsigned NOT NULL,
+  revactor_timestamp varchar(14) NOT NULL CONSTRAINT DF_revactor_timestamp DEFAULT '',
+  revactor_page int unsigned NOT NULL,
+  CONSTRAINT PK_revision_actor_temp PRIMARY KEY (revactor_rev, revactor_actor)
+);
+CREATE UNIQUE INDEX /*i*/revactor_rev ON /*_*/revision_actor_temp (revactor_rev);
+CREATE INDEX /*i*/actor_timestamp ON /*_*/revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX /*i*/page_actor_timestamp ON /*_*/revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
 --
 -- Holds TEXT of individual page revisions.
 --
@@ -246,7 +274,8 @@ CREATE TABLE /*_*/archive (
    ar_comment NVARCHAR(255) NOT NULL CONSTRAINT DF_ar_comment DEFAULT '',
    ar_comment_id bigint unsigned NOT NULL CONSTRAINT DF_ar_comment_id DEFAULT 0 CONSTRAINT FK_ar_comment_id FOREIGN KEY REFERENCES /*_*/comment(comment_id),
    ar_user INT CONSTRAINT ar_user__user_id__fk FOREIGN KEY REFERENCES /*_*/mwuser(user_id),
-   ar_user_text NVARCHAR(255) NOT NULL,
+   ar_user_text NVARCHAR(255) NOT NULL CONSTRAINT DF_ar_user_text DEFAULT '',
+   ar_actor bigint unsigned NOT NULL CONSTRAINT DF_ar_actor DEFAULT 0,
    ar_timestamp varchar(14) NOT NULL default '',
    ar_minor_edit BIT NOT NULL DEFAULT 0,
    ar_flags NVARCHAR(255) NOT NULL,
@@ -262,6 +291,7 @@ CREATE TABLE /*_*/archive (
 );
 CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
 CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
 CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
 
 
@@ -600,6 +630,9 @@ CREATE TABLE /*_*/ipblocks (
   -- User ID who made the block.
   ipb_by int REFERENCES /*_*/mwuser(user_id) ON DELETE CASCADE,
 
+  -- Actor ID who made the block.
+  ipb_by_actor bigint unsigned NOT NULL CONSTRAINT DF_ipb_by_actor DEFAULT 0,
+
   -- User name of blocker
   ipb_by_text nvarchar(255) NOT NULL default '',
 
@@ -709,7 +742,8 @@ CREATE TABLE /*_*/image (
 
   -- user_id and user_name of uploader.
   img_user int REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL,
-  img_user_text nvarchar(255) NOT NULL,
+  img_user_text nvarchar(255) NOT NULL CONSTRAINT DF_img_user_text DEFAULT '',
+  img_actor bigint unsigned NOT NULL CONSTRAINT DF_img_actor DEFAULT 0,
 
   -- Time of the upload.
   img_timestamp nvarchar(14) NOT NULL default '',
@@ -722,6 +756,7 @@ CREATE TABLE /*_*/image (
 );
 
 CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor, img_timestamp);
 -- Used by Special:ListFiles for sort-by-size
 CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
 -- Used by Special:Newimages and Special:ListFiles
@@ -768,7 +803,8 @@ CREATE TABLE /*_*/oldimage (
   oi_description nvarchar(255) NOT NULL CONSTRAINT DF_oi_description DEFAULT '',
   oi_description_id bigint unsigned NOT NULL CONSTRAINT DF_oi_description_id DEFAULT 0 CONSTRAINT FK_oi_description_id FOREIGN KEY REFERENCES /*_*/comment(comment_id),
   oi_user int REFERENCES /*_*/mwuser(user_id),
-  oi_user_text nvarchar(255) NOT NULL,
+  oi_user_text nvarchar(255) NOT NULL CONSTRAINT DF_oi_user_text DEFAULT '',
+  oi_actor bigint unsigned NOT NULL CONSTRAINT DF_oi_actor DEFAULT 0,
   oi_timestamp varchar(14) NOT NULL default '',
 
   oi_metadata varbinary(max) NOT NULL,
@@ -783,6 +819,7 @@ CREATE TABLE /*_*/oldimage (
 );
 
 CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
 CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
 CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
 
@@ -830,7 +867,8 @@ CREATE TABLE /*_*/filearchive (
   fa_description nvarchar(255) CONSTRAINT DF_fa_description DEFAULT '',
   fa_description_id bigint unsigned NOT NULL CONSTRAINT DF_fa_description DEFAULT 0 CONSTRAINT FK_fa_description FOREIGN KEY REFERENCES /*_*/comment(comment_id),
   fa_user int default 0 REFERENCES /*_*/mwuser(user_id) ON DELETE SET NULL,
-  fa_user_text nvarchar(255),
+  fa_user_text nvarchar(255) CONSTRAINT DF_fa_user_text DEFAULT '',
+  fa_actor bigint unsigned NOT NULL CONSTRAINT DF_fa_actor DEFAULT 0,
   fa_timestamp varchar(14) default '',
 
   -- Visibility of deleted revisions, bitfield
@@ -851,6 +889,7 @@ CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_sto
 CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
 -- sort by uploader
 CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
 -- find file by sha1, 10 bytes will be enough for hashes to be indexed
 CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1);
 
@@ -923,7 +962,8 @@ CREATE TABLE /*_*/recentchanges (
 
   -- As in revision
   rc_user int NOT NULL default 0 CONSTRAINT rc_user__user_id__fk FOREIGN KEY REFERENCES /*_*/mwuser(user_id),
-  rc_user_text nvarchar(255) NOT NULL,
+  rc_user_text nvarchar(255) NOT NULL CONSTRAINT DF_rc_user_text DEFAULT '',
+  rc_actor bigint unsigned NOT NULL CONSTRAINT DF_rc_actor DEFAULT 0,
 
   -- When pages are renamed, their RC entries do _not_ change.
   rc_namespace int NOT NULL default 0,
@@ -995,6 +1035,8 @@ CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,
 CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
 CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
 CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
+CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
 CREATE INDEX /*i*/rc_name_type_patrolled_timestamp ON /*_*/recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
 
 
@@ -1126,6 +1168,9 @@ CREATE TABLE /*_*/logging (
   -- Name of the user who performed this action
   log_user_text nvarchar(255) NOT NULL default '',
 
+  -- The actor who performed this action
+  log_actor bigint unsigned NOT NULL CONSTRAINT DF_log_actor DEFAULT 0,
+
   -- Key to the page affected. Where a user is the target,
   -- this will point to the user page.
   log_namespace int NOT NULL default 0,
@@ -1156,6 +1201,8 @@ CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
 CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
 CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp);
 CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp);
+CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
+CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
 
 INSERT INTO /*_*/logging (log_user,log_page,log_params) VALUES(0,0,'');
 
diff --git a/maintenance/oracle/archives/patch-actor-table.sql b/maintenance/oracle/archives/patch-actor-table.sql
new file mode 100644 (file)
index 0000000..93c7531
--- /dev/null
@@ -0,0 +1,64 @@
+--
+-- patch-actor-table.sql
+--
+-- T167246. Add an `actor` table and various columns (and temporary tables) to reference it.
+
+define mw_prefix='{$wgDBprefix}';
+
+CREATE SEQUENCE actor_actor_id_seq;
+CREATE TABLE &mw_prefix.actor (
+  actor_id NUMBER NOT NULL,
+  actor_user NUMBER,
+  actor_name VARCHAR2(255) NOT NULL
+);
+
+ALTER TABLE &mw_prefix.actor ADD CONSTRAINT &mw_prefix.actor_pk PRIMARY KEY (actor_id);
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.actor_seq_trg BEFORE INSERT ON &mw_prefix.actor
+       FOR EACH ROW WHEN (new.actor_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(actor_actor_id_seq.nextval, :new.actor_id);
+END;
+/*$mw$*/
+
+-- Create a dummy actor to satisfy fk contraints
+INSERT INTO &mw_prefix.actor (actor_id, actor_name) VALUES (0,'##Anonymous##');
+
+CREATE TABLE &mw_prefix.revision_actor_temp (
+  revactor_rev NUMBER NOT NULL,
+  revactor_actor NUMBER NOT NULL,
+  revactor_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+  revactor_page NUMBER NOT NULL
+);
+ALTER TABLE &mw_prefix.revision_actor_temp ADD CONSTRAINT &mw_prefix.revision_actor_temp_pk PRIMARY KEY (revactor_rev, revactor_actor);
+CREATE UNIQUE INDEX &mw_prefix.revactor_rev ON &mw_prefix.revision_actor_temp (revactor_rev);
+CREATE INDEX &mw_prefix.actor_timestamp ON &mw_prefix.revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX &mw_prefix.page_actor_timestamp ON &mw_prefix.revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
+ALTER TABLE &mw_prefix.archive ALTER COLUMN ar_user_text VARCHAR2(255) NULL;
+ALTER TABLE &mw_prefix.archive ADD COLUMN ar_actor NUMBER DEFAULT 0 NOT NULL;
+CREATE INDEX &mw_prefix.ar_actor_timestamp ON &mw_prefix.archive (ar_actor,ar_timestamp);
+
+ALTER TABLE &mw_prefix.ipblocks ADD COLUMN ipb_by_actor NUMBER DEFUALT 0 NOT NULL;
+
+ALTER TABLE &mw_prefix.image ALTER COLUMN img_user_text VARCHAR2(255) NULL;
+ALTER TABLE &mw_prefix.image ADD COLUMN img_actor NUMBER DEFAULT 0 NOT NULL;
+CREATE INDEX &mw_prefix.img_actor_timestamp ON &mw_prefix.image (img_actor, img_timestamp);
+
+ALTER TABLE &mw_prefix.oldimage ALTER COLUMN oi_user_text VARCHAR2(255) NULL;
+ALTER TABLE &mw_prefix.oldimage ADD COLUMN oi_actor NUMBER DEFAULT 0 NOT NULL;
+CREATE INDEX &mw_prefix.oi_actor_timestamp ON &mw_prefix.oldimage (oi_actor,oi_timestamp);
+
+ALTER TABLE &mw_prefix.filearchive ALTER COLUMN fa_user_text VARCHAR2(255) NULL;
+ALTER TABLE &mw_prefix.filearchive ADD COLUMN fa_actor NUMBER DEFAULT 0 NOT NULL;
+CREATE INDEX &mw_prefix.fa_actor_timestamp ON &mw_prefix.filearchive (fa_actor,fa_timestamp);
+
+ALTER TABLE &mw_prefix.recentchanges ALTER COLUMN rc_user_text VARCHAR2(255) NULL;
+ALTER TABLE &mw_prefix.recentchanges ADD COLUMN rc_actor NUMBER DEFAULT 0 NOT NULL;
+CREATE INDEX &mw_prefix.rc_ns_actor ON &mw_prefix.recentchanges (rc_namespace, rc_actor);
+CREATE INDEX &mw_prefix.rc_actor ON &mw_prefix.recentchanges (rc_actor, rc_timestamp);
+
+ALTER TABLE &mw_prefix.logging ADD COLUMN log_actor NUMBER DEFAULT 0 NOT NULL;
+CREATE INDEX &mw_prefix.actor_time ON &mw_prefix.logging (log_actor, log_timestamp);
+CREATE INDEX &mw_prefix.log_actor_type_time ON &mw_prefix.logging (log_actor, log_type, log_timestamp);
index 7195a5e..110188d 100644 (file)
@@ -60,6 +60,26 @@ INSERT INTO &mw_prefix.mwuser
   (user_id, user_name, user_options, user_touched, user_registration, user_editcount)
   VALUES (0,'Anonymous','', current_timestamp, current_timestamp,0);
 
+CREATE SEQUENCE actor_actor_id_seq;
+CREATE TABLE &mw_prefix.actor (
+  actor_id NUMBER NOT NULL,
+  actor_user NUMBER,
+  actor_name VARCHAR2(255) NOT NULL
+);
+
+ALTER TABLE &mw_prefix.actor ADD CONSTRAINT &mw_prefix.actor_pk PRIMARY KEY (actor_id);
+
+/*$mw$*/
+CREATE TRIGGER &mw_prefix.actor_seq_trg BEFORE INSERT ON &mw_prefix.actor
+       FOR EACH ROW WHEN (new.actor_id IS NULL)
+BEGIN
+       &mw_prefix.lastval_pkg.setLastval(actor_actor_id_seq.nextval, :new.actor_id);
+END;
+/*$mw$*/
+
+-- Create a dummy actor to satisfy fk contraints
+INSERT INTO &mw_prefix.actor (actor_id, actor_name) VALUES (0,'##Anonymous##');
+
 CREATE TABLE &mw_prefix.user_groups (
   ug_user   NUMBER      DEFAULT 0 NOT NULL,
   ug_group  VARCHAR2(255)     NOT NULL,
@@ -197,6 +217,17 @@ ALTER TABLE &mw_prefix.revision_comment_temp ADD CONSTRAINT &mw_prefix.revision_
 ALTER TABLE &mw_prefix.revision_comment_temp ADD CONSTRAINT &mw_prefix.revision_comment_temp_fk2 FOREIGN KEY (revcomment_comment_id) REFERENCES &mw_prefix."COMMENT"(comment_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
 CREATE UNIQUE INDEX &mw_prefix.revcomment_rev ON &mw_prefix.revision_comment_temp (revcomment_rev);
 
+CREATE TABLE &mw_prefix.revision_actor_temp (
+  revactor_rev NUMBER NOT NULL,
+  revactor_actor NUMBER NOT NULL,
+  revactor_timestamp TIMESTAMP(6) WITH TIME ZONE NOT NULL,
+  revactor_page NUMBER NOT NULL
+);
+ALTER TABLE &mw_prefix.revision_actor_temp ADD CONSTRAINT &mw_prefix.revision_actor_temp_pk PRIMARY KEY (revactor_rev, revactor_actor);
+CREATE UNIQUE INDEX &mw_prefix.revactor_rev ON &mw_prefix.revision_actor_temp (revactor_rev);
+CREATE INDEX &mw_prefix.actor_timestamp ON &mw_prefix.revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX &mw_prefix.page_actor_timestamp ON &mw_prefix.revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
 CREATE SEQUENCE text_old_id_seq;
 CREATE TABLE &mw_prefix.pagecontent ( -- replaces reserved word 'text'
   old_id     NUMBER  NOT NULL,
@@ -221,7 +252,8 @@ CREATE TABLE &mw_prefix.archive (
   ar_comment     VARCHAR2(255),
   ar_comment_id  NUMBER DEFAULT 0 NOT NULL,
   ar_user        NUMBER          DEFAULT 0 NOT NULL,
-  ar_user_text   VARCHAR2(255)         NOT NULL,
+  ar_user_text   VARCHAR2(255)         NULL,
+  ar_actor       NUMBER          DEFAULT 0 NOT NULL,
   ar_timestamp   TIMESTAMP(6) WITH TIME ZONE  NOT NULL,
   ar_minor_edit  CHAR(1)         DEFAULT '0' NOT NULL,
   ar_flags       VARCHAR2(255),
@@ -240,6 +272,7 @@ ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_fk1 FOREIGN KEY
 ALTER TABLE &mw_prefix.archive ADD CONSTRAINT &mw_prefix.archive_fk2 FOREIGN KEY (ar_comment_id) REFERENCES &mw_prefix."COMMENT"(comment_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
 CREATE INDEX &mw_prefix.archive_i01 ON &mw_prefix.archive (ar_namespace,ar_title,ar_timestamp);
 CREATE INDEX &mw_prefix.archive_i02 ON &mw_prefix.archive (ar_user_text,ar_timestamp);
+CREATE INDEX &mw_prefix.ar_actor_timestamp ON &mw_prefix.archive (ar_actor,ar_timestamp);
 CREATE INDEX &mw_prefix.archive_i03 ON &mw_prefix.archive (ar_rev_id);
 /*$mw$*/
 CREATE TRIGGER &mw_prefix.archive_seq_trg BEFORE INSERT ON &mw_prefix.archive
@@ -439,6 +472,7 @@ CREATE TABLE &mw_prefix.ipblocks (
   ipb_user              NUMBER      DEFAULT 0 NOT  NULL,
   ipb_by                NUMBER      DEFAULT 0 NOT NULL,
   ipb_by_text           VARCHAR2(255)      NULL,
+  ipb_by_actor          NUMBER      DEFUALT 0 NOT NULL,
   ipb_reason            VARCHAR2(255)      NULL,
   ipb_reason_id         NUMBER DEFAULT 0 NOT NULL,
   ipb_timestamp         TIMESTAMP(6) WITH TIME ZONE  NOT NULL,
@@ -484,7 +518,8 @@ CREATE TABLE &mw_prefix.image (
   img_minor_mime   VARCHAR2(100) DEFAULT 'unknown',
   img_description  VARCHAR2(255),
   img_user         NUMBER       DEFAULT 0 NOT NULL,
-  img_user_text    VARCHAR2(255)      NOT NULL,
+  img_user_text    VARCHAR2(255)      NULL,
+  img_actor        NUMBER       DEFAULT 0 NOT NULL,
   img_timestamp    TIMESTAMP(6) WITH TIME ZONE,
   img_sha1         VARCHAR2(32)
 );
@@ -494,6 +529,7 @@ CREATE INDEX &mw_prefix.image_i01 ON &mw_prefix.image (img_user_text,img_timesta
 CREATE INDEX &mw_prefix.image_i02 ON &mw_prefix.image (img_size);
 CREATE INDEX &mw_prefix.image_i03 ON &mw_prefix.image (img_timestamp);
 CREATE INDEX &mw_prefix.image_i04 ON &mw_prefix.image (img_sha1);
+CREATE INDEX &mw_prefix.img_actor_timestamp ON &mw_prefix.image (img_actor, img_timestamp);
 
 CREATE TABLE &mw_prefix.image_comment_temp (
   imgcomment_name VARCHAR2(255) NOT NULL,
@@ -515,7 +551,8 @@ CREATE TABLE &mw_prefix.oldimage (
   oi_description   VARCHAR2(255),
   oi_description_id  NUMBER DEFAULT 0 NOT NULL,
   oi_user          NUMBER          DEFAULT 0 NOT NULL,
-  oi_user_text     VARCHAR2(255)         NOT NULL,
+  oi_user_text     VARCHAR2(255)         NULL,
+  oi_actor         NUMBER          DEFAULT 0 NOT NULL,
   oi_timestamp     TIMESTAMP(6) WITH TIME ZONE  NOT NULL,
   oi_metadata      CLOB,
   oi_media_type    VARCHAR2(32) DEFAULT NULL,
@@ -528,6 +565,7 @@ ALTER TABLE &mw_prefix.oldimage ADD CONSTRAINT &mw_prefix.oldimage_fk1 FOREIGN K
 ALTER TABLE &mw_prefix.oldimage ADD CONSTRAINT &mw_prefix.oldimage_fk2 FOREIGN KEY (oi_user) REFERENCES &mw_prefix.mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED;
 ALTER TABLE &mw_prefix.oldimage ADD CONSTRAINT &mw_prefix.oldimage_fk3 FOREIGN KEY (oi_description_id) REFERENCES &mw_prefix."COMMENT"(comment_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
 CREATE INDEX &mw_prefix.oldimage_i01 ON &mw_prefix.oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX &mw_prefix.oi_actor_timestamp ON &mw_prefix.oldimage (oi_actor,oi_timestamp);
 CREATE INDEX &mw_prefix.oldimage_i02 ON &mw_prefix.oldimage (oi_name,oi_timestamp);
 CREATE INDEX &mw_prefix.oldimage_i03 ON &mw_prefix.oldimage (oi_name,oi_archive_name);
 CREATE INDEX &mw_prefix.oldimage_i04 ON &mw_prefix.oldimage (oi_sha1);
@@ -555,7 +593,8 @@ CREATE TABLE &mw_prefix.filearchive (
   fa_description        VARCHAR2(255),
   fa_description_id     NUMBER DEFAULT 0 NOT NULL,
   fa_user               NUMBER          DEFAULT 0 NOT NULL,
-  fa_user_text          VARCHAR2(255)         NOT NULL,
+  fa_user_text          VARCHAR2(255)         NULL,
+  fa_actor              NUMBER          DEFAULT 0 NOT NULL,
   fa_timestamp          TIMESTAMP(6) WITH TIME ZONE,
   fa_deleted            NUMBER      DEFAULT 0 NOT NULL,
   fa_sha1              VARCHAR2(32)
@@ -569,6 +608,7 @@ CREATE INDEX &mw_prefix.filearchive_i01 ON &mw_prefix.filearchive (fa_name, fa_t
 CREATE INDEX &mw_prefix.filearchive_i02 ON &mw_prefix.filearchive (fa_storage_group, fa_storage_key);
 CREATE INDEX &mw_prefix.filearchive_i03 ON &mw_prefix.filearchive (fa_deleted_timestamp);
 CREATE INDEX &mw_prefix.filearchive_i04 ON &mw_prefix.filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX &mw_prefix.fa_actor_timestamp ON &mw_prefix.filearchive (fa_actor,fa_timestamp);
 CREATE INDEX &mw_prefix.filearchive_i05 ON &mw_prefix.filearchive (fa_sha1);
 /*$mw$*/
 CREATE TRIGGER &mw_prefix.filearchive_seq_trg BEFORE INSERT ON &mw_prefix.filearchive
@@ -617,7 +657,8 @@ CREATE TABLE &mw_prefix.recentchanges (
   rc_timestamp       TIMESTAMP(6) WITH TIME ZONE  NOT NULL,
   rc_cur_time        TIMESTAMP(6) WITH TIME ZONE,
   rc_user            NUMBER          DEFAULT 0 NOT NULL,
-  rc_user_text       VARCHAR2(255)         NOT NULL,
+  rc_user_text       VARCHAR2(255)         NULL,
+  rc_actor           NUMBER          DEFAULT 0 NOT NULL,
   rc_namespace       NUMBER     DEFAULT 0 NOT NULL,
   rc_title           VARCHAR2(255)         NOT NULL,
   rc_comment         VARCHAR2(255),
@@ -651,6 +692,8 @@ CREATE INDEX &mw_prefix.recentchanges_i04 ON &mw_prefix.recentchanges (rc_new,rc
 CREATE INDEX &mw_prefix.recentchanges_i05 ON &mw_prefix.recentchanges (rc_ip);
 CREATE INDEX &mw_prefix.recentchanges_i06 ON &mw_prefix.recentchanges (rc_namespace, rc_user_text);
 CREATE INDEX &mw_prefix.recentchanges_i07 ON &mw_prefix.recentchanges (rc_user_text, rc_timestamp);
+CREATE INDEX &mw_prefix.rc_ns_actor ON &mw_prefix.recentchanges (rc_namespace, rc_actor);
+CREATE INDEX &mw_prefix.rc_actor ON &mw_prefix.recentchanges (rc_actor, rc_timestamp);
 CREATE INDEX &mw_prefix.recentchanges_i08 ON &mw_prefix.recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
 /*$mw$*/
 CREATE TRIGGER &mw_prefix.recentchanges_seq_trg BEFORE INSERT ON &mw_prefix.recentchanges
@@ -721,6 +764,7 @@ CREATE TABLE &mw_prefix.logging (
   log_timestamp   TIMESTAMP(6) WITH TIME ZONE  NOT NULL,
   log_user        NUMBER                DEFAULT 0 NOT NULL,
   log_user_text        VARCHAR2(255),
+  log_actor       NUMBER                DEFAULT 0 NOT NULL,
   log_namespace   NUMBER     DEFAULT 0 NOT NULL,
   log_title       VARCHAR2(255)         NOT NULL,
   log_page                             NUMBER,
@@ -739,6 +783,8 @@ CREATE INDEX &mw_prefix.logging_i04 ON &mw_prefix.logging (log_timestamp);
 CREATE INDEX &mw_prefix.logging_i05 ON &mw_prefix.logging (log_type, log_action, log_timestamp);
 CREATE INDEX &mw_prefix.logging_i06 ON &mw_prefix.logging (log_user_text, log_type, log_timestamp);
 CREATE INDEX &mw_prefix.logging_i07 ON &mw_prefix.logging (log_user_text, log_timestamp);
+CREATE INDEX &mw_prefix.actor_time ON &mw_prefix.logging (log_actor, log_timestamp);
+CREATE INDEX &mw_prefix.log_actor_type_time ON &mw_prefix.logging (log_actor, log_type, log_timestamp);
 /*$mw$*/
 CREATE TRIGGER &mw_prefix.logging_seq_trg BEFORE INSERT ON &mw_prefix.logging
        FOR EACH ROW WHEN (new.log_id IS NULL)
index 945df32..7acf6d8 100644 (file)
@@ -82,16 +82,18 @@ class Orphans extends Maintenance {
                }
 
                $commentQuery = $commentStore->getJoin( 'rev_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
 
                $this->output( "Checking for orphan revision table entries... "
                        . "(this may take a while on a large wiki)\n" );
                $result = $dbw->select(
-                       [ 'revision', 'page' ] + $commentQuery['tables'],
-                       [ 'rev_id', 'rev_page', 'rev_timestamp', 'rev_user_text' ] + $commentQuery['fields'],
+                       [ 'revision', 'page' ] + $commentQuery['tables'] + $actorQuery['tables'],
+                       [ 'rev_id', 'rev_page', 'rev_timestamp' ] + $commentQuery['fields'] + $actorQuery['fields'],
                        [ 'page_id' => null ],
                        __METHOD__,
                        [],
                        [ 'page' => [ 'LEFT JOIN', [ 'rev_page=page_id' ] ] ] + $commentQuery['joins']
+                               + $actorQuery['joins']
                );
                $orphans = $result->numRows();
                if ( $orphans > 0 ) {
index fbe16de..5174758 100644 (file)
@@ -82,13 +82,19 @@ TEXT
 
                $this->output( "Copying IP revisions to ip_changes, from rev_id $start to rev_id $end\n" );
 
+               $actorMigration = ActorMigration::newMigration();
+               $actorQuery = $actorMigration->getJoin( 'rev_user' );
+               $revUserIsAnon = $actorMigration->isAnon( $actorQuery['fields']['rev_user'] );
+
                while ( $blockStart <= $end ) {
                        $blockEnd = min( $blockStart + $this->getBatchSize(), $end );
                        $rows = $dbr->select(
-                               'revision',
-                               [ 'rev_id', 'rev_timestamp', 'rev_user_text' ],
-                               [ "rev_id BETWEEN " . (int)$blockStart . " AND " . (int)$blockEnd, 'rev_user' => 0 ],
-                               __METHOD__
+                               [ 'revision' ] + $actorQuery['tables'],
+                               [ 'rev_id', 'rev_timestamp', 'rev_user_text' => $actorQuery['fields']['rev_user_text'] ],
+                               [ "rev_id BETWEEN " . (int)$blockStart . " AND " . (int)$blockEnd, $revUserIsAnon ],
+                               __METHOD__,
+                               [],
+                               $actorQuery['joins']
                        );
 
                        $numRows = $rows->numRows();
index f960753..332d7c5 100644 (file)
@@ -53,6 +53,8 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
        }
 
        protected function doDBUpdates() {
+               global $wgActorTableSchemaMigrationStage;
+
                $batchSize = $this->getBatchSize();
                $db = $this->getDB( DB_MASTER );
                if ( !$db->tableExists( 'log_search' ) ) {
@@ -81,8 +83,8 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
                                'logging', [ 'log_id', 'log_type', 'log_action', 'log_params' ], $cond, __FUNCTION__
                        );
                        foreach ( $res as $row ) {
-                               // RevisionDelete logs - revisions
                                if ( LogEventsList::typeAction( $row, $delTypes, 'revision' ) ) {
+                                       // RevisionDelete logs - revisions
                                        $params = LogPage::extractParams( $row->log_params );
                                        // Param format: <urlparam> <item CSV> [<ofield> <nfield>]
                                        if ( count( $params ) < 2 ) {
@@ -107,30 +109,31 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
                                        $log = new LogPage( $row->log_type );
                                        // Add item relations...
                                        $log->addRelations( $field, $items, $row->log_id );
-                                       // Determine what table to query...
+                                       // Query item author relations...
                                        $prefix = substr( $field, 0, strpos( $field, '_' ) ); // db prefix
                                        if ( !isset( self::$tableMap[$prefix] ) ) {
                                                continue; // bad row?
                                        }
-                                       $table = self::$tableMap[$prefix];
-                                       $userField = $prefix . '_user';
-                                       $userTextField = $prefix . '_user_text';
-                                       // Add item author relations...
-                                       $userIds = $userIPs = [];
-                                       $sres = $db->select( $table,
-                                               [ $userField, $userTextField ],
-                                               [ $field => $items ]
-                                       );
-                                       foreach ( $sres as $srow ) {
-                                               if ( $srow->$userField > 0 ) {
-                                                       $userIds[] = intval( $srow->$userField );
-                                               } elseif ( $srow->$userTextField != '' ) {
-                                                       $userIPs[] = $srow->$userTextField;
+                                       $tables = [ self::$tableMap[$prefix] ];
+                                       $fields = [];
+                                       $joins = [];
+                                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                                               $fields['userid'] = $prefix . '_user';
+                                               $fields['username'] = $prefix . '_user_text';
+                                       }
+                                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                               if ( $prefix === 'rev' ) {
+                                                       $tables[] = 'revision_actor_temp';
+                                                       $joins['revision_actor_temp'] = [
+                                                               $wgActorTableSchemaMigrationStage === MIGRATION_NEW ? 'JOIN' : 'LEFT JOIN',
+                                                               'rev_id = revactor_rev',
+                                                       ];
+                                                       $fields['actorid'] = 'revactor_actor';
+                                               } else {
+                                                       $fields['actorid'] = $prefix . '_actor';
                                                }
                                        }
-                                       // Add item author relations...
-                                       $log->addRelations( 'target_author_id', $userIds, $row->log_id );
-                                       $log->addRelations( 'target_author_ip', $userIPs, $row->log_id );
+                                       $sres = $db->select( $tables, $fields, [ $field => $items ], __METHOD__, [], $joins );
                                } elseif ( LogEventsList::typeAction( $row, $delTypes, 'event' ) ) {
                                        // RevisionDelete logs - log events
                                        $params = LogPage::extractParams( $row->log_params );
@@ -142,22 +145,49 @@ class PopulateLogSearch extends LoggedUpdateMaintenance {
                                        $log = new LogPage( $row->log_type );
                                        // Add item relations...
                                        $log->addRelations( 'log_id', $items, $row->log_id );
-                                       // Add item author relations...
-                                       $userIds = $userIPs = [];
-                                       $sres = $db->select( 'logging',
-                                               [ 'log_user', 'log_user_text' ],
-                                               [ 'log_id' => $items ]
-                                       );
-                                       foreach ( $sres as $srow ) {
-                                               if ( $srow->log_user > 0 ) {
-                                                       $userIds[] = intval( $srow->log_user );
-                                               } elseif ( IP::isIPAddress( $srow->log_user_text ) ) {
-                                                       $userIPs[] = $srow->log_user_text;
+                                       // Query item author relations...
+                                       $fields = [];
+                                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                                               $fields['userid'] = 'log_user';
+                                               $fields['username'] = 'log_user_text';
+                                       }
+                                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                               $fields['actorid'] = 'log_actor';
+                                       }
+
+                                       $sres = $db->select( 'logging', $fields, [ 'log_id' => $items ], __METHOD__ );
+                               } else {
+                                       continue;
+                               }
+
+                               // Add item author relations...
+                               $userIds = $userIPs = $userActors = [];
+                               foreach ( $sres as $srow ) {
+                                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                                               if ( $srow->userid > 0 ) {
+                                                       $userIds[] = intval( $srow->userid );
+                                               } elseif ( $srow->username != '' ) {
+                                                       $userIPs[] = $srow->username;
                                                }
                                        }
+                                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                               if ( $srow->actorid ) {
+                                                       $userActors[] = intval( $srow->actorid );
+                                               } elseif ( $srow->userid > 0 ) {
+                                                       $userActors[] = User::newFromId( $srow->userid )->getActorId( $db );
+                                               } else {
+                                                       $userActors[] = User::newFromName( $srow->username, false )->getActorId( $db );
+                                               }
+                                       }
+                               }
+                               // Add item author relations...
+                               if ( $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ) {
                                        $log->addRelations( 'target_author_id', $userIds, $row->log_id );
                                        $log->addRelations( 'target_author_ip', $userIPs, $row->log_id );
                                }
+                               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                                       $log->addRelations( 'target_author_actor', $userActors, $row->log_id );
+                               }
                        }
                        $blockStart += $batchSize;
                        $blockEnd += $batchSize;
index 2fe7ea6..55ad536 100644 (file)
@@ -58,6 +58,14 @@ class PopulateLogUsertext extends LoggedUpdateMaintenance {
                }
                $end = $db->selectField( 'logging', 'MAX(log_id)', false, __METHOD__ );
 
+               // If this is being run during an upgrade from 1.16 or earlier, this
+               // will be run before the actor table change and should continue. But
+               // if it's being run on a new installation, the field won't exist to be populated.
+               if ( !$db->fieldInfo( 'logging', 'log_user_text' ) ) {
+                       $this->output( "No log_user_text field, nothing to do.\n" );
+                       return true;
+               }
+
                # Do remaining chunk
                $end += $batchSize - 1;
                $blockStart = $start;
diff --git a/maintenance/postgres/archives/patch-actor-table.sql b/maintenance/postgres/archives/patch-actor-table.sql
new file mode 100644 (file)
index 0000000..68e5d26
--- /dev/null
@@ -0,0 +1,24 @@
+--
+-- patch-actor-table.sql
+--
+-- T167246. Add an `actor` table and various columns (and temporary tables) to reference it.
+
+CREATE SEQUENCE actor_actor_id_seq;
+CREATE TABLE actor (
+  actor_id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('actor_actor_id_seq'),
+  actor_user INTEGER,
+  actor_name TEXT NOT NULL
+);
+CREATE UNIQUE INDEX actor_user ON actor (actor_user);
+CREATE UNIQUE INDEX actor_name ON actor (actor_name);
+
+CREATE TABLE revision_actor_temp (
+  revactor_rev INTEGER NOT NULL,
+  revactor_actor INTEGER NOT NULL,
+  revactor_timestamp TIMESTAMPTZ  NOT NULL,
+  revactor_page INTEGER          NULL  REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+  PRIMARY KEY (revactor_rev, revactor_actor)
+);
+CREATE UNIQUE INDEX revactor_rev ON revision_actor_temp (revactor_rev);
+CREATE INDEX rev_actor_timestamp ON revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX rev_page_actor_timestamp ON revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
index 34de2cb..01177d8 100644 (file)
@@ -10,6 +10,7 @@ BEGIN;
 SET client_min_messages = 'ERROR';
 
 DROP SEQUENCE IF EXISTS user_user_id_seq CASCADE;
+DROP SEQUENCE IF EXISTS actor_actor_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS page_page_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS revision_rev_id_seq CASCADE;
 DROP SEQUENCE IF EXISTS comment_comment_id_seq CASCADE;
@@ -58,6 +59,15 @@ CREATE INDEX user_email_token_idx ON mwuser (user_email_token);
 INSERT INTO mwuser
   VALUES (DEFAULT,'Anonymous','',NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,now(),now());
 
+CREATE SEQUENCE actor_actor_id_seq;
+CREATE TABLE actor (
+  actor_id      INTEGER  NOT NULL  PRIMARY KEY DEFAULT nextval('actor_actor_id_seq'),
+  actor_user INTEGER,
+  actor_name    TEXT     NOT NULL
+);
+CREATE UNIQUE INDEX actor_user ON actor (actor_user);
+CREATE UNIQUE INDEX actor_name ON actor (actor_name);
+
 CREATE TABLE user_groups (
   ug_user    INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
   ug_group   TEXT         NOT NULL,
@@ -134,8 +144,8 @@ CREATE TABLE revision (
   rev_page           INTEGER          NULL  REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
   rev_text_id        INTEGER          NULL, -- FK
   rev_comment        TEXT         NOT NULL DEFAULT '',
-  rev_user           INTEGER      NOT NULL  REFERENCES mwuser(user_id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
-  rev_user_text      TEXT         NOT NULL,
+  rev_user           INTEGER      NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED,
+  rev_user_text      TEXT         NOT NULL DEFAULT '',
   rev_timestamp      TIMESTAMPTZ  NOT NULL,
   rev_minor_edit     SMALLINT     NOT NULL  DEFAULT 0,
   rev_deleted        SMALLINT     NOT NULL  DEFAULT 0,
@@ -158,6 +168,17 @@ CREATE TABLE revision_comment_temp (
 );
 CREATE UNIQUE INDEX revcomment_rev ON revision_comment_temp (revcomment_rev);
 
+CREATE TABLE revision_actor_temp (
+  revactor_rev       INTEGER NOT NULL,
+  revactor_actor     INTEGER NOT NULL,
+  revactor_timestamp TIMESTAMPTZ  NOT NULL,
+  revactor_page      INTEGER          NULL  REFERENCES page (page_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+  PRIMARY KEY (revactor_rev, revactor_actor)
+);
+CREATE UNIQUE INDEX revactor_rev ON revision_actor_temp (revactor_rev);
+CREATE INDEX rev_actor_timestamp ON revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX rev_page_actor_timestamp ON revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
 CREATE SEQUENCE ip_changes_ipc_rev_id_seq;
 
 CREATE TABLE ip_changes (
@@ -221,8 +242,9 @@ CREATE TABLE archive (
   ar_sha1           TEXT         NOT NULL DEFAULT '',
   ar_comment        TEXT         NOT NULL DEFAULT '',
   ar_comment_id     INTEGER      NOT NULL DEFAULT 0,
-  ar_user           INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  ar_user_text      TEXT         NOT NULL,
+  ar_user           INTEGER      NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+  ar_user_text      TEXT         NOT NULL DEFAULT '',
+  ar_actor          INTEGER      NOT NULL DEFAULT 0,
   ar_timestamp      TIMESTAMPTZ  NOT NULL,
   ar_minor_edit     SMALLINT     NOT NULL  DEFAULT 0,
   ar_flags          TEXT,
@@ -235,6 +257,7 @@ CREATE TABLE archive (
 );
 CREATE INDEX archive_name_title_timestamp ON archive (ar_namespace,ar_title,ar_timestamp);
 CREATE INDEX archive_user_text            ON archive (ar_user_text);
+CREATE INDEX archive_actor                ON archive (ar_actor);
 
 
 CREATE TABLE slots (
@@ -362,8 +385,9 @@ CREATE TABLE ipblocks (
   ipb_id                INTEGER      NOT NULL  PRIMARY KEY DEFAULT nextval('ipblocks_ipb_id_seq'),
   ipb_address           TEXT             NULL,
   ipb_user              INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  ipb_by                INTEGER      NOT NULL  REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+  ipb_by                INTEGER      NOT NULL  DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
   ipb_by_text           TEXT         NOT NULL  DEFAULT '',
+  ipb_by_actor          INTEGER      NOT NULL  DEFAULT 0,
   ipb_reason            TEXT         NOT NULL  DEFAULT '',
   ipb_reason_id         INTEGER      NOT NULL  DEFAULT 0,
   ipb_timestamp         TIMESTAMPTZ  NOT NULL,
@@ -397,8 +421,9 @@ CREATE TABLE image (
   img_major_mime   TEXT                DEFAULT 'unknown',
   img_minor_mime   TEXT                DEFAULT 'unknown',
   img_description  TEXT      NOT NULL  DEFAULT '',
-  img_user         INTEGER       NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  img_user_text    TEXT      NOT NULL,
+  img_user         INTEGER   NOT NULL  DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+  img_user_text    TEXT      NOT NULL  DEFAULT '',
+  img_actor        INTEGER   NOT NULL  DEFAULT 0,
   img_timestamp    TIMESTAMPTZ,
   img_sha1         TEXT      NOT NULL  DEFAULT ''
 );
@@ -422,8 +447,9 @@ CREATE TABLE oldimage (
   oi_bits          SMALLINT         NULL,
   oi_description   TEXT         NOT NULL DEFAULT '',
   oi_description_id INTEGER     NOT NULL DEFAULT 0,
-  oi_user          INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  oi_user_text     TEXT         NOT NULL,
+  oi_user          INTEGER      NOT NULL DEFAULT 0  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+  oi_user_text     TEXT         NOT NULL DEFAULT '',
+  oi_actor         INTEGER      NOT NULL DEFAULT 0,
   oi_timestamp     TIMESTAMPTZ      NULL,
   oi_metadata      BYTEA        NOT NULL DEFAULT '',
   oi_media_type    TEXT             NULL,
@@ -459,8 +485,9 @@ CREATE TABLE filearchive (
   fa_minor_mime         TEXT                   DEFAULT 'unknown',
   fa_description        TEXT         NOT NULL DEFAULT '',
   fa_description_id     INTEGER      NOT NULL DEFAULT 0,
-  fa_user               INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  fa_user_text          TEXT         NOT NULL,
+  fa_user               INTEGER      NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+  fa_user_text          TEXT         NOT NULL DEFAULT '',
+  fa_actor              INTEGER      NOT NULL DEFAULT 0,
   fa_timestamp          TIMESTAMPTZ,
   fa_deleted            SMALLINT     NOT NULL DEFAULT 0,
   fa_sha1               TEXT         NOT NULL DEFAULT ''
@@ -504,8 +531,9 @@ CREATE TABLE recentchanges (
   rc_id              INTEGER      NOT NULL  PRIMARY KEY DEFAULT nextval('recentchanges_rc_id_seq'),
   rc_timestamp       TIMESTAMPTZ  NOT NULL,
   rc_cur_time        TIMESTAMPTZ      NULL,
-  rc_user            INTEGER          NULL  REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
-  rc_user_text       TEXT         NOT NULL,
+  rc_user            INTEGER      NOT NULL  DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+  rc_user_text       TEXT         NOT NULL  DEFAULT '',
+  rc_actor           INTEGER      NOT NULL  DEFAULT 0,
   rc_namespace       SMALLINT     NOT NULL,
   rc_title           TEXT         NOT NULL,
   rc_comment         TEXT         NOT NULL  DEFAULT '',
@@ -605,7 +633,8 @@ CREATE TABLE logging (
   log_type        TEXT         NOT NULL,
   log_action      TEXT         NOT NULL,
   log_timestamp   TIMESTAMPTZ  NOT NULL,
-  log_user        INTEGER                REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+  log_user        INTEGER      NOT NULL DEFAULT 0 REFERENCES mwuser(user_id) ON DELETE SET NULL DEFERRABLE INITIALLY DEFERRED,
+  log_actor       INTEGER      NOT NULL DEFAULT 0,
   log_namespace   SMALLINT     NOT NULL,
   log_title       TEXT         NOT NULL,
   log_comment     TEXT         NOT NULL DEFAULT '',
@@ -617,12 +646,15 @@ CREATE TABLE logging (
 );
 CREATE INDEX logging_type_name ON logging (log_type, log_timestamp);
 CREATE INDEX logging_user_time ON logging (log_timestamp, log_user);
+CREATE INDEX logging_actor_time_backwards ON logging (log_timestamp, log_actor);
 CREATE INDEX logging_page_time ON logging (log_namespace, log_title, log_timestamp);
 CREATE INDEX logging_times ON logging (log_timestamp);
 CREATE INDEX logging_user_type_time ON logging (log_user, log_type, log_timestamp);
+CREATE INDEX logging_actor_type_time ON logging (log_actor, log_type, log_timestamp);
 CREATE INDEX logging_page_id_time ON logging (log_page, log_timestamp);
 CREATE INDEX logging_user_text_type_time ON logging (log_user_text, log_type, log_timestamp);
 CREATE INDEX logging_user_text_time ON logging (log_user_text, log_timestamp);
+CREATE INDEX logging_actor_time ON logging (log_actor, log_timestamp);
 
 CREATE TABLE log_search (
   ls_field   TEXT     NOT NULL,
index 6f88f3d..c91d4ed 100644 (file)
@@ -23,6 +23,8 @@
  * @licence GNU General Public Licence 2.0 or later
  */
 
+use Wikimedia\Rdbms\IDatabase;
+
 require_once __DIR__ . '/Maintenance.php';
 
 /**
@@ -74,27 +76,35 @@ class ReassignEdits extends Maintenance {
         * @return int Number of entries changed, or that would be changed
         */
        private function doReassignEdits( &$from, &$to, $rc = false, $report = false ) {
+               global $wgActorTableSchemaMigrationStage;
+
                $dbw = $this->getDB( DB_MASTER );
                $this->beginTransaction( $dbw, __METHOD__ );
 
                # Count things
                $this->output( "Checking current edits..." );
+               $revQueryInfo = ActorMigration::newMigration()->getWhere( $dbw, 'rev_user', $from );
                $res = $dbw->select(
-                       'revision',
+                       [ 'revision' ] + $revQueryInfo['tables'],
                        'COUNT(*) AS count',
-                       $this->userConditions( $from, 'rev_user', 'rev_user_text' ),
-                       __METHOD__
+                       $revQueryInfo['conds'],
+                       __METHOD__,
+                       [],
+                       $revQueryInfo['joins']
                );
                $row = $dbw->fetchObject( $res );
                $cur = $row->count;
                $this->output( "found {$cur}.\n" );
 
                $this->output( "Checking deleted edits..." );
+               $arQueryInfo = ActorMigration::newMigration()->getWhere( $dbw, 'ar_user', $from, false );
                $res = $dbw->select(
-                       'archive',
+                       [ 'archive' ] + $arQueryInfo['tables'],
                        'COUNT(*) AS count',
-                       $this->userConditions( $from, 'ar_user', 'ar_user_text' ),
-                       __METHOD__
+                       $arQueryInfo['conds'],
+                       __METHOD__,
+                       [],
+                       $arQueryInfo['joins']
                );
                $row = $dbw->fetchObject( $res );
                $del = $row->count;
@@ -103,11 +113,14 @@ class ReassignEdits extends Maintenance {
                # Don't count recent changes if we're not supposed to
                if ( $rc ) {
                        $this->output( "Checking recent changes..." );
+                       $rcQueryInfo = ActorMigration::newMigration()->getWhere( $dbw, 'rc_user', $from, false );
                        $res = $dbw->select(
-                               'recentchanges',
+                               [ 'recentchanges' ] + $rcQueryInfo['tables'],
                                'COUNT(*) AS count',
-                               $this->userConditions( $from, 'rc_user', 'rc_user_text' ),
-                               __METHOD__
+                               $rcQueryInfo['conds'],
+                               __METHOD__,
+                               [],
+                               $rcQueryInfo['joins']
                        );
                        $row = $dbw->fetchObject( $res );
                        $rec = $row->count;
@@ -123,17 +136,38 @@ class ReassignEdits extends Maintenance {
                        if ( $total ) {
                                # Reassign edits
                                $this->output( "\nReassigning current edits..." );
-                               $dbw->update( 'revision', $this->userSpecification( $to, 'rev_user', 'rev_user_text' ),
-                                       $this->userConditions( $from, 'rev_user', 'rev_user_text' ), __METHOD__ );
+                               if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                                       $dbw->update(
+                                               'revision',
+                                               [
+                                                       'rev_user' => $to->getId(),
+                                                       'rev_user_text' =>
+                                                               $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $to->getName() : ''
+                                               ],
+                                               $from->isLoggedIn()
+                                                       ? [ 'rev_user' => $from->getId() ] : [ 'rev_user_text' => $from->getName() ],
+                                               __METHOD__
+                                       );
+                               }
+                               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                                       $dbw->update(
+                                               'revision_actor_temp',
+                                               [ 'revactor_actor' => $to->getActorId( $dbw ) ],
+                                               [ 'revactor_actor' => $from->getActorId() ],
+                                               __METHOD__
+                                       );
+                               }
                                $this->output( "done.\nReassigning deleted edits..." );
-                               $dbw->update( 'archive', $this->userSpecification( $to, 'ar_user', 'ar_user_text' ),
-                                       $this->userConditions( $from, 'ar_user', 'ar_user_text' ), __METHOD__ );
+                               $dbw->update( 'archive',
+                                       $this->userSpecification( $dbw, $to, 'ar_user', 'ar_user_text', 'ar_actor' ),
+                                       [ $arQueryInfo['conds'] ], __METHOD__ );
                                $this->output( "done.\n" );
                                # Update recent changes if required
                                if ( $rc ) {
                                        $this->output( "Updating recent changes..." );
-                                       $dbw->update( 'recentchanges', $this->userSpecification( $to, 'rc_user', 'rc_user_text' ),
-                                               $this->userConditions( $from, 'rc_user', 'rc_user_text' ), __METHOD__ );
+                                       $dbw->update( 'recentchanges',
+                                               $this->userSpecification( $dbw, $to, 'rc_user', 'rc_user_text', 'rc_actor' ),
+                                               [ $rcQueryInfo['conds'] ], __METHOD__ );
                                        $this->output( "done.\n" );
                                }
                        }
@@ -144,32 +178,31 @@ class ReassignEdits extends Maintenance {
                return (int)$total;
        }
 
-       /**
-        * Return the most efficient set of user conditions
-        * i.e. a user => id mapping, or a user_text => text mapping
-        *
-        * @param User $user User for the condition
-        * @param string $idfield Field name containing the identifier
-        * @param string $utfield Field name containing the user text
-        * @return array
-        */
-       private function userConditions( &$user, $idfield, $utfield ) {
-               return $user->getId()
-                       ? [ $idfield => $user->getId() ]
-                       : [ $utfield => $user->getName() ];
-       }
-
        /**
         * Return user specifications
         * i.e. user => id, user_text => text
         *
+        * @param IDatabase $dbw Database handle
         * @param User $user User for the spec
         * @param string $idfield Field name containing the identifier
         * @param string $utfield Field name containing the user text
+        * @param string $acfield Field name containing the actor ID
         * @return array
         */
-       private function userSpecification( &$user, $idfield, $utfield ) {
-               return [ $idfield => $user->getId(), $utfield => $user->getName() ];
+       private function userSpecification( IDatabase $dbw, &$user, $idfield, $utfield, $acfield ) {
+               global $wgActorTableSchemaMigrationStage;
+
+               $ret = [];
+               if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                       $ret += [
+                               $idfield => $user->getId(),
+                               $utfield => $wgActorTableSchemaMigrationStage <= MIGRATION_WRITE_BOTH ? $user->getName() : '',
+                       ];
+               }
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       $ret += [ $acfield => $user->getActorId( $dbw ) ];
+               }
+               return $ret;
        }
 
        /**
index 672bae8..6c7dbff 100644 (file)
@@ -116,12 +116,11 @@ class RebuildRecentchanges extends Maintenance {
                $this->output( "Loading from page and revision tables...\n" );
 
                $commentQuery = $commentStore->getJoin( 'rev_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'rev_user' );
                $res = $dbw->select(
-                       [ 'revision', 'page' ] + $commentQuery['tables'],
+                       [ 'revision', 'page' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        [
                                'rev_timestamp',
-                               'rev_user',
-                               'rev_user_text',
                                'rev_minor_edit',
                                'rev_id',
                                'rev_deleted',
@@ -129,7 +128,7 @@ class RebuildRecentchanges extends Maintenance {
                                'page_title',
                                'page_is_new',
                                'page_id'
-                       ] + $commentQuery['fields'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
                        [
                                'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
                                'rev_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
@@ -138,19 +137,19 @@ class RebuildRecentchanges extends Maintenance {
                        [ 'ORDER BY' => 'rev_timestamp DESC' ],
                        [
                                'page' => [ 'JOIN', 'rev_page=page_id' ],
-                       ] + $commentQuery['joins']
+                       ] + $commentQuery['joins'] + $actorQuery['joins']
                );
 
                $this->output( "Inserting from page and revision tables...\n" );
                $inserted = 0;
+               $actorMigration = ActorMigration::newMigration();
                foreach ( $res as $row ) {
                        $comment = $commentStore->getComment( 'rev_comment', $row );
+                       $user = User::newFromAnyId( $row->rev_user, $row->rev_user_text, $row->rev_actor );
                        $dbw->insert(
                                'recentchanges',
                                [
                                        'rc_timestamp' => $row->rev_timestamp,
-                                       'rc_user' => $row->rev_user,
-                                       'rc_user_text' => $row->rev_user_text,
                                        'rc_namespace' => $row->page_namespace,
                                        'rc_title' => $row->page_title,
                                        'rc_minor' => $row->rev_minor_edit,
@@ -162,7 +161,8 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_type' => $row->page_is_new ? RC_NEW : RC_EDIT,
                                        'rc_source' => $row->page_is_new ? RecentChange::SRC_NEW : RecentChange::SRC_EDIT,
                                        'rc_deleted' => $row->rev_deleted
-                               ] + $commentStore->insert( $dbw, 'rc_comment', $comment ),
+                               ] + $commentStore->insert( $dbw, 'rc_comment', $comment )
+                                       + $actorMigration->getInsertValues( $dbw, 'rc_user', $user ),
                                __METHOD__
                        );
                        if ( ( ++$inserted % $this->getBatchSize() ) == 0 ) {
@@ -274,12 +274,11 @@ class RebuildRecentchanges extends Maintenance {
                $this->output( "Loading from user, page, and logging tables...\n" );
 
                $commentQuery = $commentStore->getJoin( 'log_comment' );
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
                $res = $dbw->select(
-                       [ 'user', 'logging', 'page' ] + $commentQuery['tables'],
+                       [ 'logging', 'page' ] + $commentQuery['tables'] + $actorQuery['tables'],
                        [
                                'log_timestamp',
-                               'log_user',
-                               'user_name',
                                'log_namespace',
                                'log_title',
                                'page_id',
@@ -288,11 +287,10 @@ class RebuildRecentchanges extends Maintenance {
                                'log_id',
                                'log_params',
                                'log_deleted'
-                       ] + $commentQuery['fields'],
+                       ] + $commentQuery['fields'] + $actorQuery['fields'],
                        [
                                'log_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
                                'log_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
-                               'log_user=user_id',
                                // Some logs don't go in RC since they are private.
                                // @FIXME: core/extensions also have spammy logs that don't go in RC.
                                'log_type' => array_diff( $wgLogTypes, array_keys( $wgLogRestrictions ) ),
@@ -302,20 +300,20 @@ class RebuildRecentchanges extends Maintenance {
                        [
                                'page' =>
                                        [ 'LEFT JOIN', [ 'log_namespace=page_namespace', 'log_title=page_title' ] ]
-                       ] + $commentQuery['joins']
+                       ] + $commentQuery['joins'] + $actorQuery['joins']
                );
 
                $field = $dbw->fieldInfo( 'recentchanges', 'rc_cur_id' );
 
                $inserted = 0;
+               $actorMigration = ActorMigration::newMigration();
                foreach ( $res as $row ) {
                        $comment = $commentStore->getComment( 'log_comment', $row );
+                       $user = User::newFromAnyId( $row->log_user, $row->log_user_text, $row->log_actor );
                        $dbw->insert(
                                'recentchanges',
                                [
                                        'rc_timestamp' => $row->log_timestamp,
-                                       'rc_user' => $row->log_user,
-                                       'rc_user_text' => $row->user_name,
                                        'rc_namespace' => $row->log_namespace,
                                        'rc_title' => $row->log_title,
                                        'rc_minor' => 0,
@@ -334,7 +332,8 @@ class RebuildRecentchanges extends Maintenance {
                                        'rc_logid' => $row->log_id,
                                        'rc_params' => $row->log_params,
                                        'rc_deleted' => $row->log_deleted
-                               ] + $commentStore->insert( $dbw, 'rc_comment', $comment ),
+                               ] + $commentStore->insert( $dbw, 'rc_comment', $comment )
+                                       + $actorMigration->getInsertValues( $dbw, 'rc_user', $user ),
                                __METHOD__
                        );
 
@@ -352,8 +351,7 @@ class RebuildRecentchanges extends Maintenance {
 
                $dbw = $this->getDB( DB_MASTER );
 
-               list( $recentchanges, $usergroups, $user ) =
-                       $dbw->tableNamesN( 'recentchanges', 'user_groups', 'user' );
+               $userQuery = User::getQueryInfo();
 
                # @FIXME: recognize other bot account groups (not the same as users with 'bot' rights)
                # @NOTE: users with 'bot' rights choose when edits are bot edits or not. That information
@@ -363,32 +361,42 @@ class RebuildRecentchanges extends Maintenance {
 
                # Flag our recent bot edits
                if ( $botgroups ) {
-                       $botwhere = $dbw->makeList( $botgroups );
-
                        $this->output( "Flagging bot account edits...\n" );
 
                        # Find all users that are bots
-                       $sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
-                               "WHERE ug_group IN($botwhere) AND user_id = ug_user";
-                       $res = $dbw->query( $sql, __METHOD__ );
+                       $res = $dbw->select(
+                               array_merge( [ 'user_groups' ], $userQuery['tables'] ),
+                               $userQuery['fields'],
+                               [ 'ug_group' => $botgroups ],
+                               __METHOD__,
+                               [ 'DISTINCT' ],
+                               [ 'user_group' => [ 'JOIN', 'user_id = ug_user' ] ] + $userQuery['joins']
+                       );
 
                        $botusers = [];
                        foreach ( $res as $obj ) {
-                               $botusers[] = $obj->user_name;
+                               $botusers[] = User::newFromRow( $obj );
                        }
 
                        # Fill in the rc_bot field
                        if ( $botusers ) {
-                               $rcids = $dbw->selectFieldValues(
-                                       'recentchanges',
-                                       'rc_id',
-                                       [
-                                               'rc_user_text' => $botusers,
-                                               "rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
-                                               "rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) )
-                                       ],
-                                       __METHOD__
-                               );
+                               $actorQuery = ActorMigration::newMigration()->getWhere( $dbw, 'rc_user', $botusers, false );
+                               $rcids = [];
+                               foreach ( $actorQuery['orconds'] as $cond ) {
+                                       $rcids = array_merge( $rcids, $dbw->selectFieldValues(
+                                               [ 'recentchanges' ] + $actorQuery['tables'],
+                                               'rc_id',
+                                               [
+                                                       "rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
+                                                       "rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
+                                                       $cond,
+                                               ],
+                                               __METHOD__,
+                                               [],
+                                               $actorQuery['joins']
+                                       ) );
+                               }
+                               $rcids = array_values( array_unique( $rcids ) );
 
                                foreach ( array_chunk( $rcids, $this->getBatchSize() ) as $rcidBatch ) {
                                        $dbw->update(
@@ -404,28 +412,40 @@ class RebuildRecentchanges extends Maintenance {
 
                # Flag our recent autopatrolled edits
                if ( !$wgMiserMode && $autopatrolgroups ) {
-                       $patrolwhere = $dbw->makeList( $autopatrolgroups );
                        $patrolusers = [];
 
                        $this->output( "Flagging auto-patrolled edits...\n" );
 
                        # Find all users in RC with autopatrol rights
-                       $sql = "SELECT DISTINCT user_name FROM $usergroups, $user " .
-                               "WHERE ug_group IN($patrolwhere) AND user_id = ug_user";
-                       $res = $dbw->query( $sql, __METHOD__ );
+                       $res = $dbw->select(
+                               array_merge( [ 'user_groups' ], $userQuery['tables'] ),
+                               $userQuery['fields'],
+                               [ 'ug_group' => $autopatrolgroups ],
+                               __METHOD__,
+                               [ 'DISTINCT' ],
+                               [ 'user_group' => [ 'JOIN', 'user_id = ug_user' ] ] + $userQuery['joins']
+                       );
 
                        foreach ( $res as $obj ) {
-                               $patrolusers[] = $dbw->addQuotes( $obj->user_name );
+                               $patrolusers[] = User::newFromRow( $obj );
                        }
 
                        # Fill in the rc_patrolled field
                        if ( $patrolusers ) {
-                               $patrolwhere = implode( ',', $patrolusers );
-                               $sql2 = "UPDATE $recentchanges SET rc_patrolled=1 " .
-                                       "WHERE rc_user_text IN($patrolwhere) " .
-                                       "AND rc_timestamp > " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ) . ' ' .
-                                       "AND rc_timestamp < " . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) );
-                               $dbw->query( $sql2 );
+                               $actorQuery = ActorMigration::newMigration()->getWhere( $dbw, 'rc_user', $patrolusers, false );
+                               foreach ( $actorQuery['orconds'] as $cond ) {
+                                       $dbw->update(
+                                               'recentchanges',
+                                               [ 'rc_patrolled' => 1 ],
+                                               [
+                                                       $cond,
+                                                       'rc_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffFrom ) ),
+                                                       'rc_timestamp < ' . $dbw->addQuotes( $dbw->timestamp( $this->cutoffTo ) ),
+                                               ],
+                                               __METHOD__
+                                       );
+                                       wfGetLBFactory()->waitForReplication();
+                               }
                        }
                }
        }
index 0910c9c..e066300 100644 (file)
@@ -39,13 +39,27 @@ class RemoveUnusedAccounts extends Maintenance {
        }
 
        public function execute() {
+               global $wgActorTableSchemaMigrationStage;
+
                $this->output( "Remove unused accounts\n\n" );
 
                # Do an initial scan for inactive accounts and report the result
                $this->output( "Checking for unused user accounts...\n" );
-               $del = [];
+               $delUser = [];
+               $delActor = [];
                $dbr = $this->getDB( DB_REPLICA );
-               $res = $dbr->select( 'user', [ 'user_id', 'user_name', 'user_touched' ], '', __METHOD__ );
+               if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                       $res = $dbr->select(
+                               [ 'user', 'actor' ],
+                               [ 'user_id', 'user_name', 'user_touched', 'actor_id' ],
+                               '',
+                               __METHOD__,
+                               [],
+                               [ 'actor' => [ 'LEFT JOIN', 'user_id = actor_user' ] ]
+                       );
+               } else {
+                       $res = $dbr->select( 'user', [ 'user_id', 'user_name', 'user_touched' ], '', __METHOD__ );
+               }
                if ( $this->hasOption( 'ignore-groups' ) ) {
                        $excludedGroups = explode( ',', $this->getOption( 'ignore-groups' ) );
                } else {
@@ -61,27 +75,49 @@ class RemoveUnusedAccounts extends Maintenance {
                        # group or if it's touched within the $touchedSeconds seconds.
                        $instance = User::newFromId( $row->user_id );
                        if ( count( array_intersect( $instance->getEffectiveGroups(), $excludedGroups ) ) == 0
-                               && $this->isInactiveAccount( $row->user_id, true )
+                               && $this->isInactiveAccount( $row->user_id, $row->actor_id, true )
                                && wfTimestamp( TS_UNIX, $row->user_touched ) < wfTimestamp( TS_UNIX, time() - $touchedSeconds )
                        ) {
                                # Inactive; print out the name and flag it
-                               $del[] = $row->user_id;
+                               $delUser[] = $row->user_id;
+                               if ( $row->actor_id ) {
+                                       $delActor[] = $row->actor_id;
+                               }
                                $this->output( $row->user_name . "\n" );
                        }
                }
-               $count = count( $del );
+               $count = count( $delUser );
                $this->output( "...found {$count}.\n" );
 
                # If required, go back and delete each marked account
                if ( $count > 0 && $this->hasOption( 'delete' ) ) {
                        $this->output( "\nDeleting unused accounts..." );
                        $dbw = $this->getDB( DB_MASTER );
-                       $dbw->delete( 'user', [ 'user_id' => $del ], __METHOD__ );
-                       $dbw->delete( 'user_groups', [ 'ug_user' => $del ], __METHOD__ );
-                       $dbw->delete( 'user_former_groups', [ 'ufg_user' => $del ], __METHOD__ );
-                       $dbw->delete( 'user_properties', [ 'up_user' => $del ], __METHOD__ );
-                       $dbw->delete( 'logging', [ 'log_user' => $del ], __METHOD__ );
-                       $dbw->delete( 'recentchanges', [ 'rc_user' => $del ], __METHOD__ );
+                       $dbw->delete( 'user', [ 'user_id' => $delUser ], __METHOD__ );
+                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                               # Keep actor rows referenced from ipblocks
+                               $keep = $dbw->selectFieldValues(
+                                       'ipblocks', 'ipb_by_actor', [ 'ipb_by_actor' => $delActor ], __METHOD__
+                               );
+                               $del = array_diff( $delActor, $keep );
+                               if ( $del ) {
+                                       $dbw->delete( 'actor', [ 'actor_id' => $del ], __METHOD__ );
+                               }
+                               if ( $keep ) {
+                                       $dbw->update( 'actor', [ 'actor_user' => 0 ], [ 'actor_id' => $keep ], __METHOD__ );
+                               }
+                       }
+                       $dbw->delete( 'user_groups', [ 'ug_user' => $delUser ], __METHOD__ );
+                       $dbw->delete( 'user_former_groups', [ 'ufg_user' => $delUser ], __METHOD__ );
+                       $dbw->delete( 'user_properties', [ 'up_user' => $delUser ], __METHOD__ );
+                       if ( $wgActorTableSchemaMigrationStage > MIGRATION_OLD ) {
+                               $dbw->delete( 'logging', [ 'log_actor' => $delActor ], __METHOD__ );
+                               $dbw->delete( 'recentchanges', [ 'rc_actor' => $delActor ], __METHOD__ );
+                       }
+                       if ( $wgActorTableSchemaMigrationStage < MIGRATION_NEW ) {
+                               $dbw->delete( 'logging', [ 'log_user' => $delUser ], __METHOD__ );
+                               $dbw->delete( 'recentchanges', [ 'rc_user' => $delUser ], __METHOD__ );
+                       }
                        $this->output( "done.\n" );
                        # Update the site_stats.ss_users field
                        $users = $dbw->selectField( 'user', 'COUNT(*)', [], __METHOD__ );
@@ -102,10 +138,11 @@ class RemoveUnusedAccounts extends Maintenance {
         * (No edits, no deleted edits, no log entries, no current/old uploads)
         *
         * @param int $id User's ID
+        * @param int $actor User's actor ID
         * @param bool $master Perform checking on the master
         * @return bool
         */
-       private function isInactiveAccount( $id, $master = false ) {
+       private function isInactiveAccount( $id, $actor, $master = false ) {
                $dbo = $this->getDB( $master ? DB_MASTER : DB_REPLICA );
                $checks = [
                        'revision' => 'rev',
@@ -116,14 +153,37 @@ class RemoveUnusedAccounts extends Maintenance {
                ];
                $count = 0;
 
+               $migration = ActorMigration::newMigration();
+
+               $user = User::newFromAnyId( $id, null, $actor );
+
                $this->beginTransaction( $dbo, __METHOD__ );
-               foreach ( $checks as $table => $fprefix ) {
-                       $conds = [ $fprefix . '_user' => $id ];
-                       $count += (int)$dbo->selectField( $table, 'COUNT(*)', $conds, __METHOD__ );
+               foreach ( $checks as $table => $prefix ) {
+                       $actorQuery = $migration->getWhere(
+                               $dbo, $prefix . '_user', $user, $prefix !== 'oi' && $prefix !== 'fa'
+                       );
+                       $count += (int)$dbo->selectField(
+                               [ $table ] + $actorQuery['tables'],
+                               'COUNT(*)',
+                               $actorQuery['conds'],
+                               __METHOD__,
+                               [],
+                               $actorQuery['joins']
+                       );
                }
 
-               $conds = [ 'log_user' => $id, 'log_type != ' . $dbo->addQuotes( 'newusers' ) ];
-               $count += (int)$dbo->selectField( 'logging', 'COUNT(*)', $conds, __METHOD__ );
+               $actorQuery = $migration->getWhere( $dbo, 'log_user', $user, false );
+               $count += (int)$dbo->selectField(
+                       [ 'logging' ] + $actorQuery['tables'],
+                       'COUNT(*)',
+                       [
+                               $actorQuery['conds'],
+                               'log_type != ' . $dbo->addQuotes( 'newusers' )
+                       ],
+                       __METHOD__,
+                       [],
+                       $actorQuery['joins']
+               );
 
                $this->commitTransaction( $dbo, __METHOD__ );
 
index 34bc62b..878eb9b 100644 (file)
@@ -91,20 +91,26 @@ class RollbackEdits extends Maintenance {
 
        /**
         * Get all pages that should be rolled back for a given user
-        * @param string $user A name to check against rev_user_text
+        * @param string $user A name to check against
         * @return array
         */
        private function getRollbackTitles( $user ) {
                $dbr = $this->getDB( DB_REPLICA );
                $titles = [];
-               $results = $dbr->select(
-                       [ 'page', 'revision' ],
-                       [ 'page_namespace', 'page_title' ],
-                       [ 'page_latest = rev_id', 'rev_user_text' => $user ],
-                       __METHOD__
-               );
-               foreach ( $results as $row ) {
-                       $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
+               $actorQuery = ActorMigration::newMigration()
+                       ->getWhere( $dbr, 'rev_user', User::newFromName( $user, false ) );
+               foreach ( $actorQuery['orconds'] as $cond ) {
+                       $results = $dbr->select(
+                               [ 'page', 'revision' ] + $actorQuery['tables'],
+                               [ 'page_namespace', 'page_title' ],
+                               [ $cond ],
+                               __METHOD__,
+                               [],
+                               [ 'revision' => [ 'JOIN', 'page_latest = rev_id' ] ] + $actorQuery['joins']
+                       );
+                       foreach ( $results as $row ) {
+                               $titles[] = Title::makeTitle( $row->page_namespace, $row->page_title );
+                       }
                }
 
                return $titles;
diff --git a/maintenance/sqlite/archives/patch-actor-table.sql b/maintenance/sqlite/archives/patch-actor-table.sql
new file mode 100644 (file)
index 0000000..bf15a04
--- /dev/null
@@ -0,0 +1,371 @@
+--
+-- patch-actor-table.sql
+--
+-- T167246. Add an `actor` table and various columns (and temporary tables) to reference it.
+-- Sigh, sqlite, such trouble just to change the default value of a column.
+
+CREATE TABLE /*_*/actor (
+  actor_id bigint unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  actor_user int unsigned,
+  actor_name varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/actor_user ON /*_*/actor (actor_user);
+CREATE UNIQUE INDEX /*i*/actor_name ON /*_*/actor (actor_name);
+
+CREATE TABLE /*_*/revision_actor_temp (
+  revactor_rev int unsigned NOT NULL,
+  revactor_actor bigint unsigned NOT NULL,
+  revactor_timestamp binary(14) NOT NULL default '',
+  revactor_page int unsigned NOT NULL,
+  PRIMARY KEY (revactor_rev, revactor_actor)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/revactor_rev ON /*_*/revision_actor_temp (revactor_rev);
+CREATE INDEX /*i*/actor_timestamp ON /*_*/revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX /*i*/page_actor_timestamp ON /*_*/revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/archive_tmp;
+CREATE TABLE /*_*/archive_tmp (
+  ar_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ar_namespace int NOT NULL default 0,
+  ar_title varchar(255) binary NOT NULL default '',
+  ar_text mediumblob NOT NULL,
+  ar_comment varbinary(767) NOT NULL default '',
+  ar_comment_id bigint unsigned NOT NULL DEFAULT 0,
+  ar_user int unsigned NOT NULL default 0,
+  ar_user_text varchar(255) binary NOT NULL DEFAULT '',
+  ar_actor bigint unsigned NOT NULL DEFAULT 0,
+  ar_timestamp binary(14) NOT NULL default '',
+  ar_minor_edit tinyint NOT NULL default 0,
+  ar_flags tinyblob NOT NULL,
+  ar_rev_id int unsigned,
+  ar_text_id int unsigned,
+  ar_deleted tinyint unsigned NOT NULL default 0,
+  ar_len int unsigned,
+  ar_page_id int unsigned,
+  ar_parent_id int unsigned default NULL,
+  ar_sha1 varbinary(32) NOT NULL default '',
+  ar_content_model varbinary(32) DEFAULT NULL,
+  ar_content_format varbinary(64) DEFAULT NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/archive_tmp (
+       ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_user, ar_user_text,
+       ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted,
+       ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model,
+       ar_content_format)
+  SELECT
+       ar_id, ar_namespace, ar_title, ar_text, ar_comment, ar_user, ar_user_text,
+       ar_timestamp, ar_minor_edit, ar_flags, ar_rev_id, ar_text_id, ar_deleted,
+       ar_len, ar_page_id, ar_parent_id, ar_sha1, ar_content_model,
+       ar_content_format
+  FROM /*_*/archive;
+
+DROP TABLE /*_*/archive;
+ALTER TABLE /*_*/archive_tmp RENAME TO /*_*/archive;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS ipblocks_tmp;
+CREATE TABLE /*_*/ipblocks_tmp (
+  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ipb_address tinyblob NOT NULL,
+  ipb_user int unsigned NOT NULL default 0,
+  ipb_by int unsigned NOT NULL default 0,
+  ipb_by_text varchar(255) binary NOT NULL default '',
+  ipb_by_actor bigint unsigned NOT NULL DEFAULT 0,
+  ipb_reason varbinary(767) NOT NULL default '',
+  ipb_reason_id bigint unsigned NOT NULL DEFAULT 0,
+  ipb_timestamp binary(14) NOT NULL default '',
+  ipb_auto bool NOT NULL default 0,
+  ipb_anon_only bool NOT NULL default 0,
+  ipb_create_account bool NOT NULL default 1,
+  ipb_enable_autoblock bool NOT NULL default '1',
+  ipb_expiry varbinary(14) NOT NULL default '',
+  ipb_range_start tinyblob NOT NULL,
+  ipb_range_end tinyblob NOT NULL,
+  ipb_deleted bool NOT NULL default 0,
+  ipb_block_email bool NOT NULL default 0,
+  ipb_allow_usertalk bool NOT NULL default 0,
+  ipb_parent_block_id int default NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/ipblocks_tmp (
+       ipb_id, ipb_address, ipb_user, ipb_by, ipb_by_text, ipb_reason,
+       ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+       ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+       ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id)
+  SELECT
+       ipb_id, ipb_address, ipb_user, ipb_by, ipb_by_text, ipb_reason,
+       ipb_timestamp, ipb_auto, ipb_anon_only, ipb_create_account,
+       ipb_enable_autoblock, ipb_expiry, ipb_range_start, ipb_range_end,
+       ipb_deleted, ipb_block_email, ipb_allow_usertalk, ipb_parent_block_id
+  FROM /*_*/ipblocks;
+
+DROP TABLE /*_*/ipblocks;
+ALTER TABLE /*_*/ipblocks_tmp RENAME TO /*_*/ipblocks;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE INDEX /*i*/ipb_parent_block_id ON /*_*/ipblocks (ipb_parent_block_id);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/image_tmp;
+CREATE TABLE /*_*/image_tmp (
+  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+  img_size int unsigned NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata mediumblob NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+  img_minor_mime varbinary(100) NOT NULL default "unknown",
+  img_description varbinary(767) NOT NULL default '',
+  img_user int unsigned NOT NULL default 0,
+  img_user_text varchar(255) binary NOT NULL DEFAULT '',
+  img_actor bigint unsigned NOT NULL DEFAULT 0,
+  img_timestamp varbinary(14) NOT NULL default '',
+  img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/image_tmp (
+       img_name, img_size, img_width, img_height, img_metadata, img_bits,
+       img_media_type, img_major_mime, img_minor_mime, img_description, img_user,
+       img_user_text, img_timestamp, img_sha1)
+  SELECT
+       img_name, img_size, img_width, img_height, img_metadata, img_bits,
+       img_media_type, img_major_mime, img_minor_mime, img_description, img_user,
+       img_user_text, img_timestamp, img_sha1
+  FROM /*_*/image;
+
+DROP TABLE /*_*/image;
+ALTER TABLE /*_*/image_tmp RENAME TO /*_*/image;
+CREATE INDEX /*i*/img_user_timestamp ON /*_*/image (img_user,img_timestamp);
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1(10));
+CREATE INDEX /*i*/img_media_mime ON /*_*/image (img_media_type,img_major_mime,img_minor_mime);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/oldimage_tmp;
+CREATE TABLE /*_*/oldimage_tmp (
+  oi_name varchar(255) binary NOT NULL default '',
+  oi_archive_name varchar(255) binary NOT NULL default '',
+  oi_size int unsigned NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description varbinary(767) NOT NULL default '',
+  oi_description_id bigint unsigned NOT NULL DEFAULT 0,
+  oi_user int unsigned NOT NULL default 0,
+  oi_user_text varchar(255) binary NOT NULL DEFAULT '',
+  oi_actor bigint unsigned NOT NULL DEFAULT 0,
+  oi_timestamp binary(14) NOT NULL default '',
+  oi_metadata mediumblob NOT NULL,
+  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") NOT NULL default "unknown",
+  oi_minor_mime varbinary(100) NOT NULL default "unknown",
+  oi_deleted tinyint unsigned NOT NULL default 0,
+  oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/oldimage_tmp (
+       oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+       oi_description, oi_user, oi_user_text, oi_timestamp, oi_metadata,
+       oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1)
+  SELECT
+       oi_name, oi_archive_name, oi_size, oi_width, oi_height, oi_bits,
+       oi_description, oi_user, oi_user_text, oi_timestamp, oi_metadata,
+       oi_media_type, oi_major_mime, oi_minor_mime, oi_deleted, oi_sha1
+  FROM /*_*/oldimage;
+
+DROP TABLE /*_*/oldimage;
+ALTER TABLE /*_*/oldimage_tmp RENAME TO /*_*/oldimage;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1(10));
+CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/filearchive_tmp;
+CREATE TABLE /*_*/filearchive_tmp (
+  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  fa_name varchar(255) binary NOT NULL default '',
+  fa_archive_name varchar(255) binary default '',
+  fa_storage_group varbinary(16),
+  fa_storage_key varbinary(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp binary(14) default '',
+  fa_deleted_reason varbinary(767) default '',
+  fa_deleted_reason_id bigint unsigned NOT NULL DEFAULT 0,
+  fa_size int unsigned default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata mediumblob,
+  fa_bits int default 0,
+  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart", "chemical") default "unknown",
+  fa_minor_mime varbinary(100) default "unknown",
+  fa_description varbinary(767) default '',
+  fa_description_id bigint unsigned NOT NULL DEFAULT 0,
+  fa_user int unsigned default 0,
+  fa_user_text varchar(255) binary DEFAULT '',
+  fa_actor bigint unsigned NOT NULL DEFAULT 0,
+  fa_timestamp binary(14) default '',
+  fa_deleted tinyint unsigned NOT NULL default 0,
+  fa_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/filearchive_tmp (
+       fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+       fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason, fa_size,
+       fa_width, fa_height, fa_metadata, fa_bits, fa_media_type, fa_major_mime,
+       fa_minor_mime, fa_description, fa_user, fa_user_text, fa_timestamp,
+       fa_deleted, fa_sha1)
+  SELECT
+       fa_id, fa_name, fa_archive_name, fa_storage_group, fa_storage_key,
+       fa_deleted_user, fa_deleted_timestamp, fa_deleted_reason, fa_size,
+       fa_width, fa_height, fa_metadata, fa_bits, fa_media_type, fa_major_mime,
+       fa_minor_mime, fa_description, fa_user, fa_user_text, fa_timestamp,
+       fa_deleted, fa_sha1
+  FROM /*_*/filearchive;
+
+DROP TABLE /*_*/filearchive;
+ALTER TABLE /*_*/filearchive_tmp RENAME TO /*_*/filearchive;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
+CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/logging_tmp;
+CREATE TABLE /*_*/logging_tmp (
+  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  log_type varbinary(32) NOT NULL default '',
+  log_action varbinary(32) NOT NULL default '',
+  log_timestamp binary(14) NOT NULL default '19700101000000',
+  log_user int unsigned NOT NULL default 0,
+  log_user_text varchar(255) binary NOT NULL default '',
+  log_actor bigint unsigned NOT NULL DEFAULT 0,
+  log_namespace int NOT NULL default 0,
+  log_title varchar(255) binary NOT NULL default '',
+  log_page int unsigned NULL,
+  log_comment varbinary(767) NOT NULL default '',
+  log_comment_id bigint unsigned NOT NULL DEFAULT 0,
+  log_params blob NOT NULL,
+  log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/logging_tmp (
+       log_id, log_type, log_action, log_timestamp, log_user, log_user_text,
+       log_namespace, log_title, log_page, log_comment, log_comment_id,
+       log_params, log_deleted)
+  SELECT
+       log_id, log_type, log_action, log_timestamp, log_user, log_user_text,
+       log_namespace, log_title, log_page, log_comment, log_comment_id,
+       log_params, log_deleted
+  FROM /*_*/logging;
+
+DROP TABLE /*_*/logging;
+ALTER TABLE /*_*/logging_tmp RENAME TO /*_*/logging;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE INDEX /*i*/type_action ON /*_*/logging (log_type, log_action, log_timestamp);
+CREATE INDEX /*i*/log_user_text_type_time ON /*_*/logging (log_user_text, log_type, log_timestamp);
+CREATE INDEX /*i*/log_user_text_time ON /*_*/logging (log_user_text, log_timestamp);
+
+COMMIT;
+
+BEGIN;
+
+DROP TABLE IF EXISTS /*_*/recentchanges_tmp;
+CREATE TABLE /*_*/recentchanges_tmp (
+  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rc_timestamp varbinary(14) NOT NULL default '',
+  rc_user int unsigned NOT NULL default 0,
+  rc_user_text varchar(255) binary NOT NULL DEFAULT '',
+  rc_actor bigint unsigned NOT NULL DEFAULT 0,
+  rc_namespace int NOT NULL default 0,
+  rc_title varchar(255) binary NOT NULL default '',
+  rc_comment varbinary(767) NOT NULL default '',
+  rc_comment_id bigint unsigned NOT NULL DEFAULT 0,
+  rc_minor tinyint unsigned NOT NULL default 0,
+  rc_bot tinyint unsigned NOT NULL default 0,
+  rc_new tinyint unsigned NOT NULL default 0,
+  rc_cur_id int unsigned NOT NULL default 0,
+  rc_this_oldid int unsigned NOT NULL default 0,
+  rc_last_oldid int unsigned NOT NULL default 0,
+  rc_type tinyint unsigned NOT NULL default 0,
+  rc_source varchar(16) binary not null default '',
+  rc_patrolled tinyint unsigned NOT NULL default 0,
+  rc_ip varbinary(40) NOT NULL default '',
+  rc_old_len int,
+  rc_new_len int,
+  rc_deleted tinyint unsigned NOT NULL default 0,
+  rc_logid int unsigned NOT NULL default 0,
+  rc_log_type varbinary(255) NULL default NULL,
+  rc_log_action varbinary(255) NULL default NULL,
+  rc_params blob NULL
+) /*$wgDBTableOptions*/;
+
+INSERT OR IGNORE INTO /*_*/recentchanges_tmp (
+       rc_id, rc_timestamp, rc_user, rc_user_text, rc_namespace, rc_title,
+       rc_comment, rc_comment_id, rc_minor, rc_bot, rc_new, rc_cur_id,
+       rc_this_oldid, rc_last_oldid, rc_type, rc_source, rc_patrolled, rc_ip,
+       rc_old_len, rc_new_len, rc_deleted, rc_logid, rc_log_type, rc_log_action,
+       rc_params)
+  SELECT
+       rc_id, rc_timestamp, rc_user, rc_user_text, rc_namespace, rc_title,
+       rc_comment, rc_comment_id, rc_minor, rc_bot, rc_new, rc_cur_id,
+       rc_this_oldid, rc_last_oldid, rc_type, rc_source, rc_patrolled, rc_ip,
+       rc_old_len, rc_new_len, rc_deleted, rc_logid, rc_log_type, rc_log_action,
+       rc_params
+  FROM /*_*/recentchanges;
+
+DROP TABLE /*_*/recentchanges;
+ALTER TABLE /*_*/recentchanges_tmp RENAME TO /*_*/recentchanges;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
+CREATE INDEX /*i*/rc_name_type_patrolled_timestamp ON /*_*/recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
+
+COMMIT;
index b881d7e..d633a9c 100644 (file)
@@ -140,6 +140,28 @@ CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
 CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
 
 
+--
+-- The "actor" table associates user names or IP addresses with integers for
+-- the benefit of other tables that need to refer to either logged-in or
+-- logged-out users. If something can only ever be done by logged-in users, it
+-- can refer to the user table directly.
+--
+CREATE TABLE /*_*/actor (
+  -- Unique ID to identify each actor
+  actor_id bigint unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+  -- Key to user.user_id, or NULL for anonymous edits.
+  actor_user int unsigned,
+
+  -- Text username or IP address
+  actor_name varchar(255) binary NOT NULL
+) /*$wgDBTableOptions*/;
+
+-- User IDs and names must be unique.
+CREATE UNIQUE INDEX /*i*/actor_user ON /*_*/actor (actor_user);
+CREATE UNIQUE INDEX /*i*/actor_name ON /*_*/actor (actor_name);
+
+
 --
 -- User permissions have been broken out to a separate table;
 -- this allows sites with a shared user table to have different
@@ -351,9 +373,11 @@ CREATE TABLE /*_*/revision (
 
   -- Key to user.user_id of the user who made this edit.
   -- Stores 0 for anonymous edits and for some mass imports.
+  -- Deprecated in favor of revision_actor_temp.revactor_actor.
   rev_user int unsigned NOT NULL default 0,
 
   -- Text username or IP address of the editor.
+  -- Deprecated in favor of revision_actor_temp.revactor_actor.
   rev_user_text varchar(255) binary NOT NULL default '',
 
   -- Timestamp of when revision was created
@@ -425,6 +449,29 @@ CREATE TABLE /*_*/revision_comment_temp (
 -- Ensure uniqueness
 CREATE UNIQUE INDEX /*i*/revcomment_rev ON /*_*/revision_comment_temp (revcomment_rev);
 
+--
+-- Temporary table to avoid blocking on an alter of revision.
+--
+-- On large wikis like the English Wikipedia, altering the revision table is a
+-- months-long process. This table is being created to avoid such an alter, and
+-- will be merged back into revision in the future.
+--
+CREATE TABLE /*_*/revision_actor_temp (
+  -- Key to rev_id
+  revactor_rev int unsigned NOT NULL,
+  -- Key to actor_id
+  revactor_actor bigint unsigned NOT NULL,
+  -- Copy fields from revision for indexes
+  revactor_timestamp binary(14) NOT NULL default '',
+  revactor_page int unsigned NOT NULL,
+  PRIMARY KEY (revactor_rev, revactor_actor)
+) /*$wgDBTableOptions*/;
+-- Ensure uniqueness
+CREATE UNIQUE INDEX /*i*/revactor_rev ON /*_*/revision_actor_temp (revactor_rev);
+-- Match future indexes on revision
+CREATE INDEX /*i*/actor_timestamp ON /*_*/revision_actor_temp (revactor_actor,revactor_timestamp);
+CREATE INDEX /*i*/page_actor_timestamp ON /*_*/revision_actor_temp (revactor_page,revactor_actor,revactor_timestamp);
+
 --
 -- Every time an edit by a logged out user is saved,
 -- a row is created in ip_changes. This stores
@@ -547,8 +594,9 @@ CREATE TABLE /*_*/archive (
   -- Basic revision stuff...
   ar_comment varbinary(767) NOT NULL default '', -- Deprecated in favor of ar_comment_id
   ar_comment_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ar_comment should be used)
-  ar_user int unsigned NOT NULL default 0,
-  ar_user_text varchar(255) binary NOT NULL,
+  ar_user int unsigned NOT NULL default 0, -- Deprecated in favor of ar_actor
+  ar_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of ar_actor
+  ar_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ar_user/ar_user_text should be used)
   ar_timestamp binary(14) NOT NULL default '',
   ar_minor_edit tinyint NOT NULL default 0,
 
@@ -606,6 +654,7 @@ CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar
 
 -- Index for Special:DeletedContributions
 CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_actor_timestamp ON /*_*/archive (ar_actor,ar_timestamp);
 
 -- Index for linking archive rows with tables that normally link with revision
 -- rows, such as change_tag.
@@ -975,10 +1024,13 @@ CREATE TABLE /*_*/ipblocks (
   ipb_user int unsigned NOT NULL default 0,
 
   -- User ID who made the block.
-  ipb_by int unsigned NOT NULL default 0,
+  ipb_by int unsigned NOT NULL default 0, -- Deprecated in favor of ipb_by_actor
 
   -- User name of blocker
-  ipb_by_text varchar(255) binary NOT NULL default '',
+  ipb_by_text varchar(255) binary NOT NULL default '', -- Deprecated in favor of ipb_by_actor
+
+  -- Actor who made the block.
+  ipb_by_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that ipb_by/ipb_by_text should be used)
 
   -- Text comment made by blocker. Deprecated in favor of ipb_reason_id
   ipb_reason varbinary(767) NOT NULL default '',
@@ -1096,8 +1148,13 @@ CREATE TABLE /*_*/image (
   img_description varbinary(767) NOT NULL default '',
 
   -- user_id and user_name of uploader.
+  -- Deprecated in favor of img_actor.
   img_user int unsigned NOT NULL default 0,
-  img_user_text varchar(255) binary NOT NULL,
+  img_user_text varchar(255) binary NOT NULL DEFAULT '',
+
+  -- actor_id of the uploader.
+  -- ("DEFAULT 0" is temporary, signaling that img_user/img_user_text should be used)
+  img_actor bigint unsigned NOT NULL DEFAULT 0,
 
   -- Time of the upload.
   img_timestamp varbinary(14) NOT NULL default '',
@@ -1109,6 +1166,7 @@ CREATE TABLE /*_*/image (
 -- Used by Special:Newimages and ApiQueryAllImages
 CREATE INDEX /*i*/img_user_timestamp ON /*_*/image (img_user,img_timestamp);
 CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_actor_timestamp ON /*_*/image (img_actor,img_timestamp);
 -- Used by Special:ListFiles for sort-by-size
 CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
 -- Used by Special:Newimages and Special:ListFiles
@@ -1156,8 +1214,9 @@ CREATE TABLE /*_*/oldimage (
   oi_bits int NOT NULL default 0,
   oi_description varbinary(767) NOT NULL default '', -- Deprecated.
   oi_description_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that oi_description should be used)
-  oi_user int unsigned NOT NULL default 0,
-  oi_user_text varchar(255) binary NOT NULL,
+  oi_user int unsigned NOT NULL default 0, -- Deprecated in favor of oi_actor
+  oi_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of oi_actor
+  oi_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that oi_user/oi_user_text should be used)
   oi_timestamp binary(14) NOT NULL default '',
 
   oi_metadata mediumblob NOT NULL,
@@ -1169,6 +1228,7 @@ CREATE TABLE /*_*/oldimage (
 ) /*$wgDBTableOptions*/;
 
 CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_actor_timestamp ON /*_*/oldimage (oi_actor,oi_timestamp);
 CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
 -- oi_archive_name truncated to 14 to avoid key length overflow
 CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
@@ -1217,8 +1277,9 @@ CREATE TABLE /*_*/filearchive (
   fa_minor_mime varbinary(100) default "unknown",
   fa_description varbinary(767) default '', -- Deprecated
   fa_description_id bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that fa_description should be used)
-  fa_user int unsigned default 0,
-  fa_user_text varchar(255) binary,
+  fa_user int unsigned default 0, -- Deprecated in favor of fa_actor
+  fa_user_text varchar(255) binary DEFAULT '', -- Deprecated in favor of fa_actor
+  fa_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that fa_user/fa_user_text should be used)
   fa_timestamp binary(14) default '',
 
   -- Visibility of deleted revisions, bitfield
@@ -1236,6 +1297,7 @@ CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_sto
 CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
 -- sort by uploader
 CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE INDEX /*i*/fa_actor_timestamp ON /*_*/filearchive (fa_actor,fa_timestamp);
 -- find file by sha1, 10 bytes will be enough for hashes to be indexed
 CREATE INDEX /*i*/fa_sha1 ON /*_*/filearchive (fa_sha1(10));
 
@@ -1306,8 +1368,9 @@ CREATE TABLE /*_*/recentchanges (
   rc_timestamp varbinary(14) NOT NULL default '',
 
   -- As in revision
-  rc_user int unsigned NOT NULL default 0,
-  rc_user_text varchar(255) binary NOT NULL,
+  rc_user int unsigned NOT NULL default 0, -- Deprecated in favor of rc_actor
+  rc_user_text varchar(255) binary NOT NULL DEFAULT '', -- Deprecated in favor of rc_actor
+  rc_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that rc_user/rc_user_text should be used)
 
   -- When pages are renamed, their RC entries do _not_ change.
   rc_namespace int NOT NULL default 0,
@@ -1390,9 +1453,11 @@ CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
 
 -- Probably intended for Special:NewPages namespace filter
 CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_ns_actor ON /*_*/recentchanges (rc_namespace, rc_actor);
 
 -- SiteStats active user count, Special:ActiveUsers, Special:NewPages user filter
 CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE INDEX /*i*/rc_actor ON /*_*/recentchanges (rc_actor, rc_timestamp);
 
 -- ApiQueryRecentChanges (T140108)
 CREATE INDEX /*i*/rc_name_type_patrolled_timestamp ON /*_*/recentchanges (rc_namespace, rc_type, rc_patrolled, rc_timestamp);
@@ -1532,10 +1597,13 @@ CREATE TABLE /*_*/logging (
   log_timestamp binary(14) NOT NULL default '19700101000000',
 
   -- The user who performed this action; key to user_id
-  log_user int unsigned NOT NULL default 0,
+  log_user int unsigned NOT NULL default 0, -- Deprecated in favor of log_actor
 
   -- Name of the user who performed this action
-  log_user_text varchar(255) binary NOT NULL default '',
+  log_user_text varchar(255) binary NOT NULL default '', -- Deprecated in favor of log_actor
+
+  -- The actor who performed this action
+  log_actor bigint unsigned NOT NULL DEFAULT 0, -- ("DEFAULT 0" is temporary, signaling that log_user/log_user_text should be used)
 
   -- Key to the page affected. Where a user is the target,
   -- this will point to the user page.
@@ -1564,6 +1632,7 @@ CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
 
 -- Special:Log performer filter
 CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/actor_time ON /*_*/logging (log_actor, log_timestamp);
 
 -- Special:Log title filter, log extract
 CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
@@ -1573,6 +1642,7 @@ CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
 
 -- Special:Log filter by performer and type
 CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_actor_type_time ON /*_*/logging (log_actor, log_type, log_timestamp);
 
 -- Apparently just used for a few maintenance pages (findMissingFiles.php, Flow).
 -- Could be removed?
index 2465441..bf31024 100644 (file)
@@ -161,8 +161,8 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'jquery.byteLimit' => [
-               'scripts' => 'resources/src/jquery/jquery.byteLimit.js',
-               'dependencies' => 'mediawiki.String',
+               'dependencies' => 'jquery.lengthLimit',
+               'deprecated' => 'Use "jquery.lengthLimit" instead.',
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'jquery.checkboxShiftClick' => [
@@ -268,6 +268,11 @@ return [
                ],
                'targets' => [ 'desktop', 'mobile' ],
        ],
+       'jquery.lengthLimit' => [
+               'scripts' => 'resources/src/jquery/jquery.lengthLimit.js',
+               'dependencies' => 'mediawiki.String',
+               'targets' => [ 'desktop', 'mobile' ],
+       ],
        'jquery.localize' => [
                'scripts' => 'resources/src/jquery/jquery.localize.js',
        ],
@@ -1065,7 +1070,7 @@ return [
                ],
                'dependencies' => [
                        'mediawiki.RegExp',
-                       'jquery.byteLimit',
+                       'jquery.lengthLimit',
                ],
                'messages' => [
                        'htmlform-chosen-placeholder',
@@ -1427,7 +1432,7 @@ return [
                        'mediawiki.editfont.styles',
                        'jquery.textSelection',
                        'oojs-ui-core',
-                       'mediawiki.widgets.visibleByteLimit',
+                       'mediawiki.widgets.visibleLengthLimit',
                        'mediawiki.api',
                ],
        ],
@@ -1929,6 +1934,7 @@ return [
                        'recentchanges-timeout',
                        'recentchanges-network',
                        'recentchanges-notargetpage',
+                       'allpagesbadtitle',
                        'quotation-marks',
                ],
                'dependencies' => [
@@ -2100,7 +2106,7 @@ return [
        'mediawiki.special.movePage' => [
                'scripts' => 'resources/src/mediawiki.special/mediawiki.special.movePage.js',
                'dependencies' => [
-                       'mediawiki.widgets.visibleByteLimit',
+                       'mediawiki.widgets.visibleLengthLimit',
                        'mediawiki.widgets',
                ],
        ],
@@ -2314,7 +2320,7 @@ return [
        ],
        'mediawiki.legacy.protect' => [
                'scripts' => 'resources/src/mediawiki.legacy/protect.js',
-               'dependencies' => 'jquery.byteLimit',
+               'dependencies' => 'jquery.lengthLimit',
                'messages' => [ 'protect-unchain-permissions' ]
        ],
        // Used in the web installer. Test it after modifying this definition!
@@ -2480,12 +2486,17 @@ return [
                'targets' => [ 'desktop', 'mobile' ],
        ],
        'mediawiki.widgets.visibleByteLimit' => [
+               'dependencies' => 'mediawiki.widgets.visibleLengthLimit',
+               'deprecated' => 'Use "mediawiki.widgets.visibleLengthLimit" instead.',
+               'targets' => [ 'desktop', 'mobile' ]
+       ],
+       'mediawiki.widgets.visibleLengthLimit' => [
                'scripts' => [
-                       'resources/src/mediawiki.widgets.visibleByteLimit/mediawiki.widgets.visibleByteLimit.js'
+                       'resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js'
                ],
                'dependencies' => [
                        'oojs-ui-core',
-                       'jquery.byteLimit',
+                       'jquery.lengthLimit',
                        'mediawiki.String',
                ],
                'targets' => [ 'desktop', 'mobile' ]
diff --git a/resources/src/jquery/jquery.byteLimit.js b/resources/src/jquery/jquery.byteLimit.js
deleted file mode 100644 (file)
index eb21846..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-/**
- * @class jQuery.plugin.byteLimit
- */
-( function ( $, mw ) {
-
-       var
-               eventKeys = [
-                       'keyup.byteLimit',
-                       'keydown.byteLimit',
-                       'change.byteLimit',
-                       'mouseup.byteLimit',
-                       'cut.byteLimit',
-                       'paste.byteLimit',
-                       'focus.byteLimit',
-                       'blur.byteLimit'
-               ].join( ' ' ),
-               trimByteLength = require( 'mediawiki.String' ).trimByteLength;
-
-       /**
-        * Utility function to trim down a string, based on byteLimit
-        * and given a safe start position. It supports insertion anywhere
-        * in the string, so "foo" to "fobaro" if limit is 4 will result in
-        * "fobo", not "foba". Basically emulating the native maxlength by
-        * reconstructing where the insertion occurred.
-        *
-        * @method trimByteLength
-        * @deprecated Use `require( 'mediawiki.String' ).trimByteLength` instead.
-        * @static
-        * @param {string} safeVal Known value that was previously returned by this
-        * function, if none, pass empty string.
-        * @param {string} newVal New value that may have to be trimmed down.
-        * @param {number} byteLimit Number of bytes the value may be in size.
-        * @param {Function} [fn] See jQuery#byteLimit.
-        * @return {Object}
-        * @return {string} return.newVal
-        * @return {boolean} return.trimmed
-        */
-       mw.log.deprecate( $, 'trimByteLength', trimByteLength,
-               'Use require( \'mediawiki.String\' ).trimByteLength instead.', '$.trimByteLength' );
-
-       /**
-        * Enforces a byte limit on an input field, so that UTF-8 entries are counted as well,
-        * when, for example, a database field has a byte limit rather than a character limit.
-        * Plugin rationale: Browser has native maxlength for number of characters, this plugin
-        * exists to limit number of bytes instead.
-        *
-        * Can be called with a custom limit (to use that limit instead of the maxlength attribute
-        * value), a filter function (in case the limit should apply to something other than the
-        * exact input value), or both. Order of parameters is important!
-        *
-        * @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute,
-        *  called with fetched value as argument.
-        * @param {Function} [fn] Function to call on the string before assessing the length.
-        * @return {jQuery}
-        * @chainable
-        */
-       $.fn.byteLimit = function ( limit, fn ) {
-               // If the first argument is the function,
-               // set fn to the first argument's value and ignore the second argument.
-               if ( $.isFunction( limit ) ) {
-                       fn = limit;
-                       limit = undefined;
-               // Either way, verify it is a function so we don't have to call
-               // isFunction again after this.
-               } else if ( !fn || !$.isFunction( fn ) ) {
-                       fn = undefined;
-               }
-
-               // The following is specific to each element in the collection.
-               return this.each( function ( i, el ) {
-                       var $el, elLimit, prevSafeVal;
-
-                       $el = $( el );
-
-                       // If no limit was passed to byteLimit(), use the maxlength value.
-                       // Can't re-use 'limit' variable because it's in the higher scope
-                       // that would affect the next each() iteration as well.
-                       // Note that we use attribute to read the value instead of property,
-                       // because in Chrome the maxLength property by default returns the
-                       // highest supported value (no indication that it is being enforced
-                       // by choice). We don't want to bind all of this for some ridiculously
-                       // high default number, unless it was explicitly set in the HTML.
-                       // Also cast to a (primitive) number (most commonly because the maxlength
-                       // attribute contains a string, but theoretically the limit parameter
-                       // could be something else as well).
-                       elLimit = Number( limit === undefined ? $el.attr( 'maxlength' ) : limit );
-
-                       // If there is no (valid) limit passed or found in the property,
-                       // skip this. The < 0 check is required for Firefox, which returns
-                       // -1  (instead of undefined) for maxLength if it is not set.
-                       if ( !elLimit || elLimit < 0 ) {
-                               return;
-                       }
-
-                       if ( fn ) {
-                               // Save function for reference
-                               $el.data( 'byteLimit.callback', fn );
-                       }
-
-                       // Remove old event handlers (if there are any)
-                       $el.off( '.byteLimit' );
-
-                       if ( fn ) {
-                               // Disable the native maxLength (if there is any), because it interferes
-                               // with the (differently calculated) byte limit.
-                               // Aside from being differently calculated (average chars with byteLimit
-                               // is lower), we also support a callback which can make it to allow longer
-                               // values (e.g. count "Foo" from "User:Foo").
-                               // maxLength is a strange property. Removing or setting the property to
-                               // undefined directly doesn't work. Instead, it can only be unset internally
-                               // by the browser when removing the associated attribute (Firefox/Chrome).
-                               // https://bugs.chromium.org/p/chromium/issues/detail?id=136004
-                               $el.removeAttr( 'maxlength' );
-
-                       } else {
-                               // If we don't have a callback the bytelimit can only be lower than the charlimit
-                               // (that is, there are no characters less than 1 byte in size). So lets (re-)enforce
-                               // the native limit for efficiency when possible (it will make the while-loop below
-                               // faster by there being less left to interate over).
-                               $el.attr( 'maxlength', elLimit );
-                       }
-
-                       // Safe base value, used to determine the path between the previous state
-                       // and the state that triggered the event handler below - and enforce the
-                       // limit approppiately (e.g. don't chop from the end if text was inserted
-                       // at the beginning of the string).
-                       prevSafeVal = '';
-
-                       // We need to listen to after the change has already happened because we've
-                       // learned that trying to guess the new value and canceling the event
-                       // accordingly doesn't work because the new value is not always as simple as:
-                       // oldValue + String.fromCharCode( e.which ); because of cut, paste, select-drag
-                       // replacements, and custom input methods and what not.
-                       // Even though we only trim input after it was changed (never prevent it), we do
-                       // listen on events that input text, because there are cases where the text has
-                       // changed while text is being entered and keyup/change will not be fired yet
-                       // (such as holding down a single key, fires keydown, and after each keydown,
-                       // we can trim the previous one).
-                       // See https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for
-                       // the order and characteristics of the key events.
-                       $el.on( eventKeys, function () {
-                               var res = trimByteLength(
-                                       prevSafeVal,
-                                       this.value,
-                                       elLimit,
-                                       fn
-                               );
-
-                               // Only set value property if it was trimmed, because whenever the
-                               // value property is set, the browser needs to re-initiate the text context,
-                               // which moves the cursor at the end the input, moving it away from wherever it was.
-                               // This is a side-effect of limiting after the fact.
-                               if ( res.trimmed === true ) {
-                                       this.value = res.newVal;
-                                       // Trigger a 'change' event to let other scripts attached to this node know that the value
-                                       // was changed. This will also call ourselves again, but that's okay, it'll be a no-op.
-                                       $el.trigger( 'change' );
-                               }
-                               // Always adjust prevSafeVal to reflect the input value. Not doing this could cause
-                               // trimByteLength to compare the new value to an empty string instead of the
-                               // old value, resulting in trimming always from the end (T42850).
-                               prevSafeVal = res.newVal;
-                       } );
-               } );
-       };
-
-       /**
-        * @class jQuery
-        * @mixins jQuery.plugin.byteLimit
-        */
-}( jQuery, mediaWiki ) );
diff --git a/resources/src/jquery/jquery.lengthLimit.js b/resources/src/jquery/jquery.lengthLimit.js
new file mode 100644 (file)
index 0000000..2738d1a
--- /dev/null
@@ -0,0 +1,204 @@
+/**
+ * @class jQuery.plugin.lengthLimit
+ */
+( function ( $, mw ) {
+
+       var
+               eventKeys = [
+                       'keyup.lengthLimit',
+                       'keydown.lengthLimit',
+                       'change.lengthLimit',
+                       'mouseup.lengthLimit',
+                       'cut.lengthLimit',
+                       'paste.lengthLimit',
+                       'focus.lengthLimit',
+                       'blur.lengthLimit'
+               ].join( ' ' ),
+               trimByteLength = require( 'mediawiki.String' ).trimByteLength,
+               trimCodePointLength = require( 'mediawiki.String' ).trimCodePointLength;
+
+       /**
+        * Utility function to trim down a string, based on byteLimit
+        * and given a safe start position. It supports insertion anywhere
+        * in the string, so "foo" to "fobaro" if limit is 4 will result in
+        * "fobo", not "foba". Basically emulating the native maxlength by
+        * reconstructing where the insertion occurred.
+        *
+        * @method trimByteLength
+        * @deprecated Use `require( 'mediawiki.String' ).trimByteLength` instead.
+        * @static
+        * @param {string} safeVal Known value that was previously returned by this
+        * function, if none, pass empty string.
+        * @param {string} newVal New value that may have to be trimmed down.
+        * @param {number} byteLimit Number of bytes the value may be in size.
+        * @param {Function} [filterFn] See jQuery#byteLimit.
+        * @return {Object}
+        * @return {string} return.newVal
+        * @return {boolean} return.trimmed
+        */
+       mw.log.deprecate( $, 'trimByteLength', trimByteLength,
+               'Use require( \'mediawiki.String\' ).trimByteLength instead.', '$.trimByteLength' );
+
+       function lengthLimit( trimFn, limit, filterFn ) {
+               var allowNativeMaxlength = trimFn === trimByteLength;
+
+               // If the first argument is the function,
+               // set filterFn to the first argument's value and ignore the second argument.
+               if ( $.isFunction( limit ) ) {
+                       filterFn = limit;
+                       limit = undefined;
+               // Either way, verify it is a function so we don't have to call
+               // isFunction again after this.
+               } else if ( !filterFn || !$.isFunction( filterFn ) ) {
+                       filterFn = undefined;
+               }
+
+               // The following is specific to each element in the collection.
+               return this.each( function ( i, el ) {
+                       var $el, elLimit, prevSafeVal;
+
+                       $el = $( el );
+
+                       // If no limit was passed to lengthLimit(), use the maxlength value.
+                       // Can't re-use 'limit' variable because it's in the higher scope
+                       // that would affect the next each() iteration as well.
+                       // Note that we use attribute to read the value instead of property,
+                       // because in Chrome the maxLength property by default returns the
+                       // highest supported value (no indication that it is being enforced
+                       // by choice). We don't want to bind all of this for some ridiculously
+                       // high default number, unless it was explicitly set in the HTML.
+                       // Also cast to a (primitive) number (most commonly because the maxlength
+                       // attribute contains a string, but theoretically the limit parameter
+                       // could be something else as well).
+                       elLimit = Number( limit === undefined ? $el.attr( 'maxlength' ) : limit );
+
+                       // If there is no (valid) limit passed or found in the property,
+                       // skip this. The < 0 check is required for Firefox, which returns
+                       // -1  (instead of undefined) for maxLength if it is not set.
+                       if ( !elLimit || elLimit < 0 ) {
+                               return;
+                       }
+
+                       if ( filterFn ) {
+                               // Save function for reference
+                               $el.data( 'lengthLimit.callback', filterFn );
+                       }
+
+                       // Remove old event handlers (if there are any)
+                       $el.off( '.lengthLimit' );
+
+                       if ( filterFn || !allowNativeMaxlength ) {
+                               // Disable the native maxLength (if there is any), because it interferes
+                               // with the (differently calculated) character/byte limit.
+                               // Aside from being differently calculated,
+                               // we also support a callback which can make it to allow longer
+                               // values (e.g. count "Foo" from "User:Foo").
+                               // maxLength is a strange property. Removing or setting the property to
+                               // undefined directly doesn't work. Instead, it can only be unset internally
+                               // by the browser when removing the associated attribute (Firefox/Chrome).
+                               // https://bugs.chromium.org/p/chromium/issues/detail?id=136004
+                               $el.removeAttr( 'maxlength' );
+
+                       } else {
+                               // For $.byteLimit only, if we don't have a callback,
+                               // the byteLimit can only be lower than the native maxLength limit
+                               // (that is, there are no characters less than 1 byte in size). So lets (re-)enforce
+                               // the native limit for efficiency when possible (it will make the while-loop below
+                               // faster by there being less left to interate over). This does not work for $.codePointLimit
+                               // (code units for surrogates represent half a character each).
+                               $el.attr( 'maxlength', elLimit );
+                       }
+
+                       // Safe base value, used to determine the path between the previous state
+                       // and the state that triggered the event handler below - and enforce the
+                       // limit approppiately (e.g. don't chop from the end if text was inserted
+                       // at the beginning of the string).
+                       prevSafeVal = '';
+
+                       // We need to listen to after the change has already happened because we've
+                       // learned that trying to guess the new value and canceling the event
+                       // accordingly doesn't work because the new value is not always as simple as:
+                       // oldValue + String.fromCharCode( e.which ); because of cut, paste, select-drag
+                       // replacements, and custom input methods and what not.
+                       // Even though we only trim input after it was changed (never prevent it), we do
+                       // listen on events that input text, because there are cases where the text has
+                       // changed while text is being entered and keyup/change will not be fired yet
+                       // (such as holding down a single key, fires keydown, and after each keydown,
+                       // we can trim the previous one).
+                       // See https://www.w3.org/TR/DOM-Level-3-Events/#events-keyboard-event-order for
+                       // the order and characteristics of the key events.
+                       $el.on( eventKeys, function () {
+                               var res = trimFn(
+                                       prevSafeVal,
+                                       this.value,
+                                       elLimit,
+                                       filterFn
+                               );
+
+                               // Only set value property if it was trimmed, because whenever the
+                               // value property is set, the browser needs to re-initiate the text context,
+                               // which moves the cursor at the end the input, moving it away from wherever it was.
+                               // This is a side-effect of limiting after the fact.
+                               if ( res.trimmed === true ) {
+                                       this.value = res.newVal;
+                                       // Trigger a 'change' event to let other scripts attached to this node know that the value
+                                       // was changed. This will also call ourselves again, but that's okay, it'll be a no-op.
+                                       $el.trigger( 'change' );
+                               }
+                               // Always adjust prevSafeVal to reflect the input value. Not doing this could cause
+                               // trimFn to compare the new value to an empty string instead of the
+                               // old value, resulting in trimming always from the end (T42850).
+                               prevSafeVal = res.newVal;
+                       } );
+               } );
+       }
+
+       /**
+        * Enforces a byte limit on an input field, assuming UTF-8 encoding, for situations
+        * when, for example, a database field has a byte limit rather than a character limit.
+        * Plugin rationale: Browser has native maxlength for number of characters (technically,
+        * UTF-16 code units), this plugin exists to limit number of bytes instead.
+        *
+        * Can be called with a custom limit (to use that limit instead of the maxlength attribute
+        * value), a filter function (in case the limit should apply to something other than the
+        * exact input value), or both. Order of parameters is important!
+        *
+        * @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute,
+        *  called with fetched value as argument.
+        * @param {Function} [filterFn] Function to call on the string before assessing the length.
+        * @return {jQuery}
+        * @chainable
+        */
+       $.fn.byteLimit = function ( limit, filterFn ) {
+               return lengthLimit.call( this, trimByteLength, limit, filterFn );
+       };
+
+       /**
+        * Enforces a codepoint (character) limit on an input field.
+        *
+        * For unfortunate historical reasons, browsers' native maxlength counts [the number of UTF-16
+        * code units rather than Unicode codepoints] [1], which means that codepoints outside the Basic
+        * Multilingual Plane (e.g. many emojis) count as 2 characters each. This plugin exists to
+        * correct this.
+        *
+        * [1]: https://www.w3.org/TR/html5/sec-forms.html#limiting-user-input-length-the-maxlength-attribute
+        *
+        * Can be called with a custom limit (to use that limit instead of the maxlength attribute
+        * value), a filter function (in case the limit should apply to something other than the
+        * exact input value), or both. Order of parameters is important!
+        *
+        * @param {number} [limit] Limit to enforce, fallsback to maxLength-attribute,
+        *  called with fetched value as argument.
+        * @param {Function} [filterFn] Function to call on the string before assessing the length.
+        * @return {jQuery}
+        * @chainable
+        */
+       $.fn.codePointLimit = function ( limit, filterFn ) {
+               return lengthLimit.call( this, trimCodePointLength, limit, filterFn );
+       };
+
+       /**
+        * @class jQuery
+        * @mixins jQuery.plugin.lengthLimit
+        */
+}( jQuery, mediaWiki ) );
index 087c5bc..a85e740 100644 (file)
 
        $( function () {
                var editBox, scrollTop, $editForm,
-                       // TODO T6714: Once this can be adjusted, read this from config.
-                       summaryByteLimit = 255,
+                       summaryCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ),
+                       summaryByteLimit = mw.config.get( 'wgCommentByteLimit' ),
                        wpSummary = OO.ui.infuse( $( '#wpSummaryWidget' ) );
 
                // Show a byte-counter to users with how many bytes are left for their edit summary.
                // TODO: This looks a bit weird, as there is no unit in the UI, just numbers; showing
                // 'bytes' confused users in testing, and showing 'chars' would be a lie. See T42035.
-               mw.widgets.visibleByteLimit( wpSummary, summaryByteLimit );
+               // (Showing 'chars' is still confusing with the code point limit, since it's not obvious
+               // that e.g. combining diacritics or zero-width punctuation count as characters.)
+               if ( summaryCodePointLimit ) {
+                       mw.widgets.visibleCodePointLimit( wpSummary, summaryCodePointLimit );
+               } else if ( summaryByteLimit ) {
+                       mw.widgets.visibleByteLimit( wpSummary, summaryByteLimit );
+               }
 
                // Restore the edit box scroll state following a preview operation,
                // and set up a form submission handler to remember this state.
index aa49ae1..b96bebc 100644 (file)
@@ -1,6 +1,9 @@
 ( function ( mw, $ ) {
+       var ProtectionForm,
+               reasonCodePointLimit = mw.config.get( 'wgCommentCodePointLimit' ),
+               reasonByteLimit = mw.config.get( 'wgCommentByteLimit' );
 
-       var ProtectionForm = window.ProtectionForm = {
+       ProtectionForm = window.ProtectionForm = {
                /**
                 * Set up the protection chaining interface (i.e. "unlock move permissions" checkbox)
                 * on the protection form
                                this.toggleUnchainedInputs( !this.areAllTypesMatching() );
                        }
 
-                       $( '#mwProtect-reason' ).byteLimit( 180 );
+                       // Arbitrary 75 to leave some space for the autogenerated null edit's summary
+                       if ( reasonCodePointLimit ) {
+                               $( '#mwProtect-reason' ).codePointLimit( reasonCodePointLimit - 75 );
+                       } else if ( reasonByteLimit ) {
+                               $( '#mwProtect-reason' ).byteLimit( reasonByteLimit - 75 );
+                       }
 
                        this.updateCascadeCheckbox();
                        return true;
index 58f6dc2..1218644 100644 (file)
        list-style-image: e( '/* @embed */' ) url( @fallback ) e( '\9' );
 }
 
+.hyphens( @value: auto ) {
+       & when ( @value = auto ){
+               // Legacy `word-wrap`; IE 6-11, Edge 12+, Firefox 3.5+, Chrome 4+, Safari 3.1+,
+               //   Opera 11.5+, iOS 3.2+, Android 2.1+
+               // `overflow-wrap` is W3 standard, but it doesn't seem as if browser vendors
+               //   will abandon `word-wrap` (it has wider support), therefore no duplication.
+               word-wrap: break-word;
+       }
+       & when ( @value = none ) {
+               word-wrap: normal;
+       }
+
+       // CSS3 hyphenation
+       -webkit-hyphens: @value; // Safari 5.1+, iOS 4.3+
+       -moz-hyphens: @value;    // Firefox 6-42
+       -ms-hyphens: @value;     // IE 10-11, Edge 12+
+       hyphens: @value;         // Firefox 43+, Chrome 55+, Android 62+, UC Browser 11.8+, Samsung 6.2+
+}
+
 .transform( @value ) {
        -webkit-transform: @value; // Safari 3.1-8.0, iOS 3.2-8.4, Android 2.1-4.4.4
        -moz-transform: @value; // Firefox 3.5-15
index dcce92d..16f58ee 100644 (file)
                                info.noResultsDetails = 'NO_RESULTS_TIMEOUT';
                        } else if ( $root.find( '.mw-changeslist-notargetpage' ).length ) {
                                info.noResultsDetails = 'NO_RESULTS_NO_TARGET_PAGE';
+                       } else if ( $root.find( '.mw-changeslist-invalidtargetpage' ).length ) {
+                               info.noResultsDetails = 'NO_RESULTS_INVALID_TARGET_PAGE';
                        } else {
                                info.noResultsDetails = 'NO_RESULTS_NORMAL';
                        }
index 1f72484..d181532 100644 (file)
                                {
                                        $topSection: $topSection,
                                        $filtersContainer: $( '.rcfilters-container' ),
-                                       $changesListContainer: $( [
-                                               '.mw-changeslist',
-                                               '.mw-changeslist-empty',
-                                               '.mw-changeslist-timeout',
-                                               '.mw-changeslist-notargetpage'
-                                       ].join( ', ' ) ),
+                                       $changesListContainer: $( '.mw-changeslist, .mw-changeslist-empty' ),
                                        $formContainer: $initialFieldset
                                }
                        );
index 413d45b..ca9b252 100644 (file)
                }
        }
 
-       // Temporarily hide any specific 'no result' message while we load rcfilters.
-       .mw-changeslist-empty,
-       .mw-changeslist-timeout,
-       .mw-changeslist-notargetpage {
+       // Temporarily hide the empty results section while we load rcfilters.
+       .mw-changeslist-empty {
+               display: none;
+       }
+
+       .errorbox {
                display: none;
        }
 
index e2092dc..b49a1cb 100644 (file)
                                                        .text( mw.msg( this.getMsgKeyForNoResults( noResultsDetails ) ) )
                                        );
 
-                               this.$element.removeClass( 'mw-changeslist-timeout' );
-                               this.$element.removeClass( 'mw-changeslist-notargetpage' );
+                               // remove all classes matching mw-changeslist-*
+                               this.$element.removeClass( function ( elementIndex, allClasses ) {
+                                       return allClasses
+                                               .split( ' ' )
+                                               .filter( function ( className ) {
+                                                       return className.indexOf( 'mw-changeslist-' ) === 0;
+                                               } )
+                                               .join( ' ' );
+                               } );
                        }
 
                        this.$element.append( $message );
                        NO_RESULTS_NORMAL: 'recentchanges-noresult',
                        NO_RESULTS_TIMEOUT: 'recentchanges-timeout',
                        NO_RESULTS_NETWORK_ERROR: 'recentchanges-network',
-                       NO_RESULTS_NO_TARGET_PAGE: 'recentchanges-notargetpage'
+                       NO_RESULTS_NO_TARGET_PAGE: 'recentchanges-notargetpage',
+                       NO_RESULTS_INVALID_TARGET_PAGE: 'allpagesbadtitle'
                };
                return reasonMsgKeyMap[ reason ];
        };
index 054f45f..69fec08 100644 (file)
@@ -4,6 +4,4 @@
 
 .mw-watch-link-disabled {
        pointer-events: none;
-       /* Fallback for older browsers not supporting pointer-events: none */
-       cursor: default;
 }
index 34c8bbd..0886f8c 100644 (file)
                                title = mw.util.getParamValue( 'title', $link.attr( 'href' ) );
                        // nice format
                        title = mw.Title.newFromText( title ).toText();
-                       // Disable link whilst we're busy to avoid double handling
-                       if ( $link.data( 'mwDisabled' ) ) {
-                               // mw-watch-link-disabled disables pointer-events which prevents the click event
-                               // from happening in the first place. In older browsers we kill the event here.
-                               return false;
-                       }
-                       $link.data( 'mwDisabled', true ).addClass( 'mw-watch-link-disabled' );
+                       $link.addClass( 'mw-watch-link-disabled' );
 
                        // Preload the notification module for mw.notify
                        mw.loader.load( 'mediawiki.notification' );
@@ -46,7 +40,7 @@
                        }
 
                        promise.always( function () {
-                               $link.data( 'mwDisabled', false ).removeClass( 'mw-watch-link-disabled' );
+                               $link.removeClass( 'mw-watch-link-disabled' );
                        } );
 
                        e.preventDefault();
diff --git a/resources/src/mediawiki.widgets.visibleByteLimit/mediawiki.widgets.visibleByteLimit.js b/resources/src/mediawiki.widgets.visibleByteLimit/mediawiki.widgets.visibleByteLimit.js
deleted file mode 100644 (file)
index 03ffca7..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-( function ( mw ) {
-
-       var byteLength = require( 'mediawiki.String' ).byteLength;
-
-       /**
-        * @class mw.widgets
-        */
-
-       /**
-        * Add a visible byte limit label to a TextInputWidget.
-        *
-        * Uses jQuery#byteLimit to enforce the limit.
-        *
-        * @param {OO.ui.TextInputWidget} textInputWidget Text input widget
-        * @param {number} [limit] Byte limit, defaults to $input's maxlength
-        */
-       mw.widgets.visibleByteLimit = function ( textInputWidget, limit ) {
-               limit = limit || +textInputWidget.$input.attr( 'maxlength' );
-
-               function updateCount() {
-                       textInputWidget.setLabel( ( limit - byteLength( textInputWidget.getValue() ) ).toString() );
-               }
-               textInputWidget.on( 'change', updateCount );
-               // Initialise value
-               updateCount();
-
-               // Actually enforce limit
-               textInputWidget.$input.byteLimit( limit );
-       };
-
-}( mediaWiki ) );
diff --git a/resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js b/resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js
new file mode 100644 (file)
index 0000000..52ebe74
--- /dev/null
@@ -0,0 +1,54 @@
+( function ( mw ) {
+
+       var byteLength = require( 'mediawiki.String' ).byteLength,
+               codePointLength = require( 'mediawiki.String' ).codePointLength;
+
+       /**
+        * @class mw.widgets
+        */
+
+       /**
+        * Add a visible byte limit label to a TextInputWidget.
+        *
+        * Uses jQuery#byteLimit to enforce the limit.
+        *
+        * @param {OO.ui.TextInputWidget} textInputWidget Text input widget
+        * @param {number} [limit] Byte limit, defaults to $input's maxlength
+        */
+       mw.widgets.visibleByteLimit = function ( textInputWidget, limit ) {
+               limit = limit || +textInputWidget.$input.attr( 'maxlength' );
+
+               function updateCount() {
+                       textInputWidget.setLabel( ( limit - byteLength( textInputWidget.getValue() ) ).toString() );
+               }
+               textInputWidget.on( 'change', updateCount );
+               // Initialise value
+               updateCount();
+
+               // Actually enforce limit
+               textInputWidget.$input.byteLimit( limit );
+       };
+
+       /**
+        * Add a visible codepoint (character) limit label to a TextInputWidget.
+        *
+        * Uses jQuery#codePointLimit to enforce the limit.
+        *
+        * @param {OO.ui.TextInputWidget} textInputWidget Text input widget
+        * @param {number} [limit] Byte limit, defaults to $input's maxlength
+        */
+       mw.widgets.visibleCodePointLimit = function ( textInputWidget, limit ) {
+               limit = limit || +textInputWidget.$input.attr( 'maxlength' );
+
+               function updateCount() {
+                       textInputWidget.setLabel( ( limit - codePointLength( textInputWidget.getValue() ) ).toString() );
+               }
+               textInputWidget.on( 'change', updateCount );
+               // Initialise value
+               updateCount();
+
+               // Actually enforce limit
+               textInputWidget.$input.codePointLimit( limit );
+       };
+
+}( mediaWiki ) );
index 5e11680..5d9bef0 100644 (file)
                        .length;
        }
 
+       /**
+        * Calculate the character length of a string (accounting for UTF-16 surrogates).
+        *
+        * @param {string} str
+        * @return {number}
+        */
+       function codePointLength( str ) {
+               return str
+                       // Low surrogate + high surrogate pairs represent one character (codepoint) each
+                       .replace( /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '*' )
+                       .length;
+       }
+
        // Like String#charAt, but return the pair of UTF-16 surrogates for characters outside of BMP.
        function codePointAt( string, offset, backwards ) {
                // We don't need to check for offsets at the beginning or end of string,
                }
        }
 
-       /**
-        * Utility function to trim down a string, based on byteLimit
-        * and given a safe start position. It supports insertion anywhere
-        * in the string, so "foo" to "fobaro" if limit is 4 will result in
-        * "fobo", not "foba". Basically emulating the native maxlength by
-        * reconstructing where the insertion occurred.
-        *
-        * @param {string} safeVal Known value that was previously returned by this
-        * function, if none, pass empty string.
-        * @param {string} newVal New value that may have to be trimmed down.
-        * @param {number} byteLimit Number of bytes the value may be in size.
-        * @param {Function} [fn] Function to call on the string before assessing the length.
-        * @return {Object}
-        * @return {string} return.newVal
-        * @return {boolean} return.trimmed
-        */
-       function trimByteLength( safeVal, newVal, byteLimit, fn ) {
+       function trimLength( safeVal, newVal, length, lengthFn ) {
                var startMatches, endMatches, matchesLen, inpParts, chopOff, oldChar, newChar,
                        oldVal = safeVal;
 
                // Run the hook if one was provided, but only on the length
                // assessment. The value itself is not to be affected by the hook.
-               if ( byteLength( fn ? fn( newVal ) : newVal ) <= byteLimit ) {
+               if ( lengthFn( newVal ) <= length ) {
                        // Limit was not reached, just remember the new value
                        // and let the user continue.
                        return {
 
                // Chop off characters from the end of the "inserted content" string
                // until the limit is statisfied.
-               if ( fn ) {
-                       // stop, when there is nothing to slice - T43450
-                       while ( byteLength( fn( inpParts.join( '' ) ) ) > byteLimit && inpParts[ 1 ].length > 0 ) {
-                               // Do not chop off halves of surrogate pairs
-                               chopOff = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test( inpParts[ 1 ] ) ? 2 : 1;
-                               inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -chopOff );
-                       }
-               } else {
-                       while ( byteLength( inpParts.join( '' ) ) > byteLimit ) {
-                               // Do not chop off halves of surrogate pairs
-                               chopOff = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test( inpParts[ 1 ] ) ? 2 : 1;
-                               inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -chopOff );
-                       }
+               // Make sure to stop when there is nothing to slice (T43450).
+               while ( lengthFn( inpParts.join( '' ) ) > length && inpParts[ 1 ].length > 0 ) {
+                       // Do not chop off halves of surrogate pairs
+                       chopOff = /[\uD800-\uDBFF][\uDC00-\uDFFF]$/.test( inpParts[ 1 ] ) ? 2 : 1;
+                       inpParts[ 1 ] = inpParts[ 1 ].slice( 0, -chopOff );
                }
 
                return {
                        newVal: inpParts.join( '' ),
-                       // For pathological fn() that always returns a value longer than the limit, we might have
+                       // For pathological lengthFn() that always returns a length greater than the limit, we might have
                        // ended up not trimming - check for this case to avoid infinite loops
                        trimmed: newVal !== inpParts.join( '' )
                };
        }
 
+       /**
+        * Utility function to trim down a string, based on byteLimit
+        * and given a safe start position. It supports insertion anywhere
+        * in the string, so "foo" to "fobaro" if limit is 4 will result in
+        * "fobo", not "foba". Basically emulating the native maxlength by
+        * reconstructing where the insertion occurred.
+        *
+        * @param {string} safeVal Known value that was previously returned by this
+        * function, if none, pass empty string.
+        * @param {string} newVal New value that may have to be trimmed down.
+        * @param {number} byteLimit Number of bytes the value may be in size.
+        * @param {Function} [filterFn] Function to call on the string before assessing the length.
+        * @return {Object}
+        * @return {string} return.newVal
+        * @return {boolean} return.trimmed
+        */
+       function trimByteLength( safeVal, newVal, byteLimit, filterFn ) {
+               var lengthFn;
+               if ( filterFn ) {
+                       lengthFn = function ( val ) {
+                               return byteLength( filterFn( val ) );
+                       };
+               } else {
+                       lengthFn = byteLength;
+               }
+
+               return trimLength( safeVal, newVal, byteLimit, lengthFn );
+       }
+
+       /**
+        * Utility function to trim down a string, based on codePointLimit
+        * and given a safe start position. It supports insertion anywhere
+        * in the string, so "foo" to "fobaro" if limit is 4 will result in
+        * "fobo", not "foba". Basically emulating the native maxlength by
+        * reconstructing where the insertion occurred.
+        *
+        * @param {string} safeVal Known value that was previously returned by this
+        * function, if none, pass empty string.
+        * @param {string} newVal New value that may have to be trimmed down.
+        * @param {number} codePointLimit Number of characters the value may be in size.
+        * @param {Function} [filterFn] Function to call on the string before assessing the length.
+        * @return {Object}
+        * @return {string} return.newVal
+        * @return {boolean} return.trimmed
+        */
+       function trimCodePointLength( safeVal, newVal, codePointLimit, filterFn ) {
+               var lengthFn;
+               if ( filterFn ) {
+                       lengthFn = function ( val ) {
+                               return codePointLength( filterFn( val ) );
+                       };
+               } else {
+                       lengthFn = codePointLength;
+               }
+
+               return trimLength( safeVal, newVal, codePointLimit, lengthFn );
+       }
+
        module.exports = {
                byteLength: byteLength,
-               trimByteLength: trimByteLength
+               codePointLength: codePointLength,
+               trimByteLength: trimByteLength,
+               trimCodePointLength: trimCodePointLength
        };
 
 }() );
index 4f6f6b6..d4b1f91 100644 (file)
@@ -1148,7 +1148,7 @@ class ParserTestRunner {
         * @return array
         */
        private function listTables() {
-               global $wgCommentTableSchemaMigrationStage;
+               global $wgCommentTableSchemaMigrationStage, $wgActorTableSchemaMigrationStage;
 
                $tables = [ 'user', 'user_properties', 'user_former_groups', 'page', 'page_restrictions',
                        'protected_titles', 'revision', 'ip_changes', 'text', 'pagelinks', 'imagelinks',
@@ -1166,6 +1166,12 @@ class ParserTestRunner {
                        $tables[] = 'image_comment_temp';
                }
 
+               if ( $wgActorTableSchemaMigrationStage >= MIGRATION_WRITE_BOTH ) {
+                       // The new tables for actors are in use
+                       $tables[] = 'actor';
+                       $tables[] = 'revision_actor_temp';
+               }
+
                if ( in_array( $this->db->getType(), [ 'mysql', 'sqlite', 'oracle' ] ) ) {
                        array_push( $tables, 'searchindex' );
                }
index 652b1ee..92c0714 100644 (file)
@@ -1435,8 +1435,9 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
         */
        private function resetDB( $db, $tablesUsed ) {
                if ( $db ) {
-                       $userTables = [ 'user', 'user_groups', 'user_properties' ];
-                       $pageTables = [ 'page', 'revision', 'ip_changes', 'revision_comment_temp', 'comment' ];
+                       $userTables = [ 'user', 'user_groups', 'user_properties', 'actor' ];
+                       $pageTables = [ 'page', 'revision', 'ip_changes', 'revision_comment_temp',
+                               'revision_actor_temp', 'comment' ];
                        $coreDBDataTables = array_merge( $userTables, $pageTables );
 
                        // If any of the user or page tables were marked as used, we should clear all of them.
diff --git a/tests/phpunit/data/media/jpeg-segment-loop1.jpg b/tests/phpunit/data/media/jpeg-segment-loop1.jpg
new file mode 100644 (file)
index 0000000..962f3fe
Binary files /dev/null and b/tests/phpunit/data/media/jpeg-segment-loop1.jpg differ
diff --git a/tests/phpunit/data/media/jpeg-segment-loop2.jpg b/tests/phpunit/data/media/jpeg-segment-loop2.jpg
new file mode 100644 (file)
index 0000000..e3a7505
Binary files /dev/null and b/tests/phpunit/data/media/jpeg-segment-loop2.jpg differ
diff --git a/tests/phpunit/data/media/jpeg-xmp-loop.jpg b/tests/phpunit/data/media/jpeg-xmp-loop.jpg
deleted file mode 100644 (file)
index 962f3fe..0000000
Binary files a/tests/phpunit/data/media/jpeg-xmp-loop.jpg and /dev/null differ
diff --git a/tests/phpunit/includes/ActorMigrationTest.php b/tests/phpunit/includes/ActorMigrationTest.php
new file mode 100644 (file)
index 0000000..1b0c848
--- /dev/null
@@ -0,0 +1,695 @@
+<?php
+
+use MediaWiki\User\UserIdentity;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Database
+ * @covers ActorMigration
+ */
+class ActorMigrationTest extends MediaWikiLangTestCase {
+
+       protected $tablesUsed = [
+               'revision',
+               'revision_actor_temp',
+               'ipblocks',
+               'recentchanges',
+               'actor',
+       ];
+
+       /**
+        * Create an ActorMigration for a particular stage
+        * @param int $stage
+        * @return ActorMigration
+        */
+       protected function makeMigration( $stage ) {
+               return new ActorMigration( $stage );
+       }
+
+       /**
+        * @dataProvider provideGetJoin
+        * @param int $stage
+        * @param string $key
+        * @param array $expect
+        */
+       public function testGetJoin( $stage, $key, $expect ) {
+               $m = $this->makeMigration( $stage );
+               $result = $m->getJoin( $key );
+               $this->assertEquals( $expect, $result );
+       }
+
+       public static function provideGetJoin() {
+               return [
+                       'Simple table, old' => [
+                               MIGRATION_OLD, 'rc_user', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'rc_user' => 'rc_user',
+                                               'rc_user_text' => 'rc_user_text',
+                                               'rc_actor' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Simple table, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rc_user', [
+                                       'tables' => [ 'actor_rc_user' => 'actor' ],
+                                       'fields' => [
+                                               'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
+                                               'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
+                                               'rc_actor' => 'rc_actor',
+                                       ],
+                                       'joins' => [
+                                               'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+                                       ],
+                               ],
+                       ],
+                       'Simple table, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rc_user', [
+                                       'tables' => [ 'actor_rc_user' => 'actor' ],
+                                       'fields' => [
+                                               'rc_user' => 'COALESCE( actor_rc_user.actor_user, rc_user )',
+                                               'rc_user_text' => 'COALESCE( actor_rc_user.actor_name, rc_user_text )',
+                                               'rc_actor' => 'rc_actor',
+                                       ],
+                                       'joins' => [
+                                               'actor_rc_user' => [ 'LEFT JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+                                       ],
+                               ],
+                       ],
+                       'Simple table, new' => [
+                               MIGRATION_NEW, 'rc_user', [
+                                       'tables' => [ 'actor_rc_user' => 'actor' ],
+                                       'fields' => [
+                                               'rc_user' => 'actor_rc_user.actor_user',
+                                               'rc_user_text' => 'actor_rc_user.actor_name',
+                                               'rc_actor' => 'rc_actor',
+                                       ],
+                                       'joins' => [
+                                               'actor_rc_user' => [ 'JOIN', 'actor_rc_user.actor_id = rc_actor' ],
+                                       ],
+                               ],
+                       ],
+
+                       'ipblocks, old' => [
+                               MIGRATION_OLD, 'ipb_by', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'ipb_by' => 'ipb_by',
+                                               'ipb_by_text' => 'ipb_by_text',
+                                               'ipb_by_actor' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'ipblocks, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'ipb_by', [
+                                       'tables' => [ 'actor_ipb_by' => 'actor' ],
+                                       'fields' => [
+                                               'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
+                                               'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
+                                               'ipb_by_actor' => 'ipb_by_actor',
+                                       ],
+                                       'joins' => [
+                                               'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+                                       ],
+                               ],
+                       ],
+                       'ipblocks, write-new' => [
+                               MIGRATION_WRITE_NEW, 'ipb_by', [
+                                       'tables' => [ 'actor_ipb_by' => 'actor' ],
+                                       'fields' => [
+                                               'ipb_by' => 'COALESCE( actor_ipb_by.actor_user, ipb_by )',
+                                               'ipb_by_text' => 'COALESCE( actor_ipb_by.actor_name, ipb_by_text )',
+                                               'ipb_by_actor' => 'ipb_by_actor',
+                                       ],
+                                       'joins' => [
+                                               'actor_ipb_by' => [ 'LEFT JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+                                       ],
+                               ],
+                       ],
+                       'ipblocks, new' => [
+                               MIGRATION_NEW, 'ipb_by', [
+                                       'tables' => [ 'actor_ipb_by' => 'actor' ],
+                                       'fields' => [
+                                               'ipb_by' => 'actor_ipb_by.actor_user',
+                                               'ipb_by_text' => 'actor_ipb_by.actor_name',
+                                               'ipb_by_actor' => 'ipb_by_actor',
+                                       ],
+                                       'joins' => [
+                                               'actor_ipb_by' => [ 'JOIN', 'actor_ipb_by.actor_id = ipb_by_actor' ],
+                                       ],
+                               ],
+                       ],
+
+                       'Revision, old' => [
+                               MIGRATION_OLD, 'rev_user', [
+                                       'tables' => [],
+                                       'fields' => [
+                                               'rev_user' => 'rev_user',
+                                               'rev_user_text' => 'rev_user_text',
+                                               'rev_actor' => 'NULL',
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Revision, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rev_user', [
+                                       'tables' => [
+                                               'temp_rev_user' => 'revision_actor_temp',
+                                               'actor_rev_user' => 'actor',
+                                       ],
+                                       'fields' => [
+                                               'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
+                                               'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
+                                               'rev_actor' => 'temp_rev_user.revactor_actor',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                               'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                                       ],
+                               ],
+                       ],
+                       'Revision, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rev_user', [
+                                       'tables' => [
+                                               'temp_rev_user' => 'revision_actor_temp',
+                                               'actor_rev_user' => 'actor',
+                                       ],
+                                       'fields' => [
+                                               'rev_user' => 'COALESCE( actor_rev_user.actor_user, rev_user )',
+                                               'rev_user_text' => 'COALESCE( actor_rev_user.actor_name, rev_user_text )',
+                                               'rev_actor' => 'temp_rev_user.revactor_actor',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                               'actor_rev_user' => [ 'LEFT JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                                       ],
+                               ],
+                       ],
+                       'Revision, new' => [
+                               MIGRATION_NEW, 'rev_user', [
+                                       'tables' => [
+                                               'temp_rev_user' => 'revision_actor_temp',
+                                               'actor_rev_user' => 'actor',
+                                       ],
+                                       'fields' => [
+                                               'rev_user' => 'actor_rev_user.actor_user',
+                                               'rev_user_text' => 'actor_rev_user.actor_name',
+                                               'rev_actor' => 'temp_rev_user.revactor_actor',
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                               'actor_rev_user' => [ 'JOIN', 'actor_rev_user.actor_id = temp_rev_user.revactor_actor' ],
+                                       ],
+                               ],
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideGetWhere
+        * @param int $stage
+        * @param string $key
+        * @param UserIdentity[] $users
+        * @param bool $useId
+        * @param array $expect
+        */
+       public function testGetWhere( $stage, $key, $users, $useId, $expect ) {
+               $expect['conds'] = '(' . implode( ') OR (', $expect['orconds'] ) . ')';
+
+               if ( count( $users ) === 1 ) {
+                       $users = reset( $users );
+               }
+
+               $m = $this->makeMigration( $stage );
+               $result = $m->getWhere( $this->db, $key, $users, $useId );
+               $this->assertEquals( $expect, $result );
+       }
+
+       public function provideGetWhere() {
+               $makeUserIdentity = function ( $id, $name, $actor ) {
+                       $u = $this->getMock( UserIdentity::class );
+                       $u->method( 'getId' )->willReturn( $id );
+                       $u->method( 'getName' )->willReturn( $name );
+                       $u->method( 'getActorId' )->willReturn( $actor );
+                       return $u;
+               };
+
+               $genericUser = [ $makeUserIdentity( 1, 'User1', 11 ) ];
+               $complicatedUsers = [
+                       $makeUserIdentity( 1, 'User1', 11 ),
+                       $makeUserIdentity( 2, 'User2', 12 ),
+                       $makeUserIdentity( 3, 'User3', 0 ),
+                       $makeUserIdentity( 0, '192.168.12.34', 34 ),
+                       $makeUserIdentity( 0, '192.168.12.35', 0 ),
+               ];
+
+               return [
+                       'Simple table, old' => [
+                               MIGRATION_OLD, 'rc_user', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [ 'userid' => "rc_user = '1'" ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Simple table, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rc_user', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "rc_actor = '11'",
+                                               'userid' => "rc_actor = '0' AND rc_user = '1'"
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Simple table, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rc_user', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "rc_actor = '11'",
+                                               'userid' => "rc_actor = '0' AND rc_user = '1'"
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Simple table, new' => [
+                               MIGRATION_NEW, 'rc_user', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [ 'actor' => "rc_actor = '11'" ],
+                                       'joins' => [],
+                               ],
+                       ],
+
+                       'ipblocks, old' => [
+                               MIGRATION_OLD, 'ipb_by', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [ 'userid' => "ipb_by = '1'" ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'ipblocks, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'ipb_by', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "ipb_by_actor = '11'",
+                                               'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'ipblocks, write-new' => [
+                               MIGRATION_WRITE_NEW, 'ipb_by', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "ipb_by_actor = '11'",
+                                               'userid' => "ipb_by_actor = '0' AND ipb_by = '1'"
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'ipblocks, new' => [
+                               MIGRATION_NEW, 'ipb_by', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [ 'actor' => "ipb_by_actor = '11'" ],
+                                       'joins' => [],
+                               ],
+                       ],
+
+                       'Revision, old' => [
+                               MIGRATION_OLD, 'rev_user', $genericUser, true, [
+                                       'tables' => [],
+                                       'orconds' => [ 'userid' => "rev_user = '1'" ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Revision, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rev_user', $genericUser, true, [
+                                       'tables' => [
+                                               'temp_rev_user' => 'revision_actor_temp',
+                                       ],
+                                       'orconds' => [
+                                               'actor' =>
+                                                       "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
+                                               'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Revision, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rev_user', $genericUser, true, [
+                                       'tables' => [
+                                               'temp_rev_user' => 'revision_actor_temp',
+                                       ],
+                                       'orconds' => [
+                                               'actor' =>
+                                                       "(temp_rev_user.revactor_actor IS NOT NULL) AND temp_rev_user.revactor_actor = '11'",
+                                               'userid' => "temp_rev_user.revactor_actor IS NULL AND rev_user = '1'"
+                                       ],
+                                       'joins' => [
+                                               'temp_rev_user' => [ 'LEFT JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       ],
+                               ],
+                       ],
+                       'Revision, new' => [
+                               MIGRATION_NEW, 'rev_user', $genericUser, true, [
+                                       'tables' => [
+                                               'temp_rev_user' => 'revision_actor_temp',
+                                       ],
+                                       'orconds' => [ 'actor' => "temp_rev_user.revactor_actor = '11'" ],
+                                       'joins' => [
+                                               'temp_rev_user' => [ 'JOIN', 'temp_rev_user.revactor_rev = rev_id' ],
+                                       ],
+                               ],
+                       ],
+
+                       'Multiple users, old' => [
+                               MIGRATION_OLD, 'rc_user', $complicatedUsers, true, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'userid' => "rc_user IN ('1','2','3') ",
+                                               'username' => "rc_user_text IN ('192.168.12.34','192.168.12.35') "
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Multiple users, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, true, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "rc_actor IN ('11','12','34') ",
+                                               'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
+                                               'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Multiple users, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, true, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "rc_actor IN ('11','12','34') ",
+                                               'userid' => "rc_actor = '0' AND rc_user IN ('1','2','3') ",
+                                               'username' => "rc_actor = '0' AND rc_user_text IN ('192.168.12.34','192.168.12.35') "
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Multiple users, new' => [
+                               MIGRATION_NEW, 'rc_user', $complicatedUsers, true, [
+                                       'tables' => [],
+                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+                                       'joins' => [],
+                               ],
+                       ],
+
+                       'Multiple users, no use ID, old' => [
+                               MIGRATION_OLD, 'rc_user', $complicatedUsers, false, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'username' => "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Multiple users, write-both' => [
+                               MIGRATION_WRITE_BOTH, 'rc_user', $complicatedUsers, false, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "rc_actor IN ('11','12','34') ",
+                                               'username' => "rc_actor = '0' AND "
+                                               . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Multiple users, write-new' => [
+                               MIGRATION_WRITE_NEW, 'rc_user', $complicatedUsers, false, [
+                                       'tables' => [],
+                                       'orconds' => [
+                                               'actor' => "rc_actor IN ('11','12','34') ",
+                                               'username' => "rc_actor = '0' AND "
+                                               . "rc_user_text IN ('User1','User2','User3','192.168.12.34','192.168.12.35') "
+                                       ],
+                                       'joins' => [],
+                               ],
+                       ],
+                       'Multiple users, new' => [
+                               MIGRATION_NEW, 'rc_user', $complicatedUsers, false, [
+                                       'tables' => [],
+                                       'orconds' => [ 'actor' => "rc_actor IN ('11','12','34') " ],
+                                       'joins' => [],
+                               ],
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideInsertRoundTrip
+        * @param string $table
+        * @param string $key
+        * @param string $pk
+        * @param array $extraFields
+        */
+       public function testInsertRoundTrip( $table, $key, $pk, $extraFields ) {
+               $u = $this->getTestUser()->getUser();
+               $user = $this->getMock( UserIdentity::class );
+               $user->method( 'getId' )->willReturn( $u->getId() );
+               $user->method( 'getName' )->willReturn( $u->getName() );
+               if ( $u->getActorId( $this->db ) ) {
+                       $user->method( 'getActorId' )->willReturn( $u->getActorId() );
+               } else {
+                       $this->db->insert(
+                               'actor',
+                               [ 'actor_user' => $u->getId(), 'actor_name' => $u->getName() ],
+                               __METHOD__
+                       );
+                       $user->method( 'getActorId' )->willReturn( $this->db->insertId() );
+               }
+
+               $stages = [
+                       MIGRATION_OLD => [ MIGRATION_OLD, MIGRATION_WRITE_NEW ],
+                       MIGRATION_WRITE_BOTH => [ MIGRATION_OLD, MIGRATION_NEW ],
+                       MIGRATION_WRITE_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+                       MIGRATION_NEW => [ MIGRATION_WRITE_BOTH, MIGRATION_NEW ],
+               ];
+
+               $nameKey = $key . '_text';
+               $actorKey = $key === 'ipb_by' ? 'ipb_by_actor' : substr( $key, 0, -5 ) . '_actor';
+
+               foreach ( $stages as $writeStage => $readRange ) {
+                       if ( $key === 'ipb_by' ) {
+                               $extraFields['ipb_address'] = __CLASS__ . "#$writeStage";
+                       }
+
+                       $w = $this->makeMigration( $writeStage );
+                       $usesTemp = $key === 'rev_user';
+
+                       if ( $usesTemp ) {
+                               list( $fields, $callback ) = $w->getInsertValuesWithTempTable( $this->db, $key, $user );
+                       } else {
+                               $fields = $w->getInsertValues( $this->db, $key, $user );
+                       }
+
+                       if ( $writeStage <= MIGRATION_WRITE_BOTH ) {
+                               $this->assertSame( $user->getId(), $fields[$key], "old field, stage=$writeStage" );
+                               $this->assertSame( $user->getName(), $fields[$nameKey], "old field, stage=$writeStage" );
+                       } else {
+                               $this->assertArrayNotHasKey( $key, $fields, "old field, stage=$writeStage" );
+                               $this->assertArrayNotHasKey( $nameKey, $fields, "old field, stage=$writeStage" );
+                       }
+                       if ( $writeStage >= MIGRATION_WRITE_BOTH && !$usesTemp ) {
+                               $this->assertSame( $user->getActorId(), $fields[$actorKey], "new field, stage=$writeStage" );
+                       } else {
+                               $this->assertArrayNotHasKey( $actorKey, $fields, "new field, stage=$writeStage" );
+                       }
+
+                       $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
+                       $id = $this->db->insertId();
+                       if ( $usesTemp ) {
+                               $callback( $id, $extraFields );
+                       }
+
+                       for ( $readStage = $readRange[0]; $readStage <= $readRange[1]; $readStage++ ) {
+                               $r = $this->makeMigration( $readStage );
+
+                               $queryInfo = $r->getJoin( $key );
+                               $row = $this->db->selectRow(
+                                       [ $table ] + $queryInfo['tables'],
+                                       $queryInfo['fields'],
+                                       [ $pk => $id ],
+                                       __METHOD__,
+                                       [],
+                                       $queryInfo['joins']
+                               );
+
+                               $this->assertSame( $user->getId(), (int)$row->$key, "w=$writeStage, r=$readStage, id" );
+                               $this->assertSame( $user->getName(), $row->$nameKey, "w=$writeStage, r=$readStage, name" );
+                               $this->assertSame(
+                                       $readStage === MIGRATION_OLD || $writeStage === MIGRATION_OLD ? 0 : $user->getActorId(),
+                                       (int)$row->$actorKey,
+                                       "w=$writeStage, r=$readStage, actor"
+                               );
+                       }
+               }
+       }
+
+       public static function provideInsertRoundTrip() {
+               $db = wfGetDB( DB_REPLICA ); // for timestamps
+
+               $ipbfields = [
+               ];
+               $revfields = [
+               ];
+
+               return [
+                       'recentchanges' => [ 'recentchanges', 'rc_user', 'rc_id', [
+                               'rc_timestamp' => $db->timestamp(),
+                               'rc_namespace' => 0,
+                               'rc_title' => 'Test',
+                               'rc_this_oldid' => 42,
+                               'rc_last_oldid' => 41,
+                               'rc_source' => 'test',
+                       ] ],
+                       'ipblocks' => [ 'ipblocks', 'ipb_by', 'ipb_id', [
+                               'ipb_range_start' => '',
+                               'ipb_range_end' => '',
+                               'ipb_timestamp' => $db->timestamp(),
+                               'ipb_expiry' => $db->getInfinity(),
+                       ] ],
+                       'revision' => [ 'revision', 'rev_user', 'rev_id', [
+                               'rev_page' => 42,
+                               'rev_text_id' => 42,
+                               'rev_len' => 0,
+                               'rev_timestamp' => $db->timestamp(),
+                       ] ],
+               ];
+       }
+
+       public static function provideStages() {
+               return [
+                       'MIGRATION_OLD' => [ MIGRATION_OLD ],
+                       'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH ],
+                       'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW ],
+                       'MIGRATION_NEW' => [ MIGRATION_NEW ],
+               ];
+       }
+
+       /**
+        * @dataProvider provideStages
+        * @param int $stage
+        * @expectedException InvalidArgumentException
+        * @expectedExceptionMessage Must use getInsertValuesWithTempTable() for rev_user
+        */
+       public function testInsertWrong( $stage ) {
+               $m = $this->makeMigration( $stage );
+               $m->getInsertValues( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+       }
+
+       /**
+        * @dataProvider provideStages
+        * @param int $stage
+        * @expectedException InvalidArgumentException
+        * @expectedExceptionMessage Must use getInsertValues() for rc_user
+        */
+       public function testInsertWithTempTableWrong( $stage ) {
+               $m = $this->makeMigration( $stage );
+               $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+       }
+
+       /**
+        * @dataProvider provideStages
+        * @param int $stage
+        */
+       public function testInsertWithTempTableDeprecated( $stage ) {
+               $wrap = TestingAccessWrapper::newFromClass( ActorMigration::class );
+               $wrap->formerTempTables += [ 'rc_user' => '1.30' ];
+
+               $this->hideDeprecated( 'ActorMigration::getInsertValuesWithTempTable for rc_user' );
+               $m = $this->makeMigration( $stage );
+               list( $fields, $callback )
+                       = $m->getInsertValuesWithTempTable( $this->db, 'rc_user', $this->getTestUser()->getUser() );
+               $this->assertTrue( is_callable( $callback ) );
+       }
+
+       /**
+        * @dataProvider provideStages
+        * @param int $stage
+        * @expectedException InvalidArgumentException
+        * @expectedExceptionMessage $extra[rev_timestamp] is not provided
+        */
+       public function testInsertWithTempTableCallbackMissingFields( $stage ) {
+               $m = $this->makeMigration( $stage );
+               list( $fields, $callback )
+                       = $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $this->getTestUser()->getUser() );
+               $callback( 1, [] );
+       }
+
+       public function testInsertUserIdentity() {
+               $user = $this->getTestUser()->getUser();
+               $userIdentity = $this->getMock( UserIdentity::class );
+               $userIdentity->method( 'getId' )->willReturn( $user->getId() );
+               $userIdentity->method( 'getName' )->willReturn( $user->getName() );
+               $userIdentity->method( 'getActorId' )->willReturn( 0 );
+
+               list( $cFields, $cCallback ) = CommentStore::newKey( 'rev_comment' )
+                       ->insertWithTempTable( $this->db, '' );
+               $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+               list( $fields, $callback ) =
+                       $m->getInsertValuesWithTempTable( $this->db, 'rev_user', $userIdentity );
+               $extraFields = [
+                       'rev_page' => 42,
+                       'rev_text_id' => 42,
+                       'rev_len' => 0,
+                       'rev_timestamp' => $this->db->timestamp(),
+               ] + $cFields;
+               $this->db->insert( 'revision', $extraFields + $fields, __METHOD__ );
+               $id = $this->db->insertId();
+               $callback( $id, $extraFields );
+               $cCallback( $id );
+
+               $qi = Revision::getQueryInfo();
+               $row = $this->db->selectRow(
+                       $qi['tables'], $qi['fields'], [ 'rev_id' => $id ], __METHOD__, [], $qi['joins']
+               );
+               $this->assertSame( $user->getId(), (int)$row->rev_user );
+               $this->assertSame( $user->getName(), $row->rev_user_text );
+               $this->assertSame( $user->getActorId(), (int)$row->rev_actor );
+
+               $m = $this->makeMigration( MIGRATION_WRITE_BOTH );
+               $fields = $m->getInsertValues( $this->db, 'dummy_user', $userIdentity );
+               $this->assertSame( $user->getId(), $fields['dummy_user'] );
+               $this->assertSame( $user->getName(), $fields['dummy_user_text'] );
+               $this->assertSame( $user->getActorId(), $fields['dummy_actor'] );
+       }
+
+       public function testConstructor() {
+               $m = ActorMigration::newMigration();
+               $this->assertInstanceOf( ActorMigration::class, $m );
+               $this->assertSame( $m, ActorMigration::newMigration() );
+       }
+
+       /**
+        * @dataProvider provideIsAnon
+        * @param int $stage
+        * @param string $isAnon
+        * @param string $isNotAnon
+        */
+       public function testIsAnon( $stage, $isAnon, $isNotAnon ) {
+               $m = $this->makeMigration( $stage );
+               $this->assertSame( $isAnon, $m->isAnon( 'foo' ) );
+               $this->assertSame( $isNotAnon, $m->isNotAnon( 'foo' ) );
+       }
+
+       public static function provideIsAnon() {
+               return [
+                       'MIGRATION_OLD' => [ MIGRATION_OLD, 'foo = 0', 'foo != 0' ],
+                       'MIGRATION_WRITE_BOTH' => [ MIGRATION_WRITE_BOTH, 'foo = 0', 'foo != 0' ],
+                       'MIGRATION_WRITE_NEW' => [ MIGRATION_WRITE_NEW, 'foo = 0', 'foo != 0' ],
+                       'MIGRATION_NEW' => [ MIGRATION_NEW, 'foo IS NULL', 'foo IS NOT NULL' ],
+               ];
+       }
+
+}
index 1e46555..19780a6 100644 (file)
@@ -35,6 +35,7 @@ class BlockTest extends MediaWikiLangTestCase {
                $blockOptions = [
                        'address' => $user->getName(),
                        'user' => $user->getId(),
+                       'by' => $this->getTestSysop()->getUser()->getId(),
                        'reason' => 'Parce que',
                        'expiry' => time() + 100500,
                ];
@@ -393,7 +394,7 @@ class BlockTest extends MediaWikiLangTestCase {
                $block = new Block(
                        /* address */ $username,
                        /* user */ 0,
-                       /* by */ 0,
+                       /* by */ $this->getTestSysop()->getUser()->getId(),
                        /* reason */ $reason,
                        /* timestamp */ 0,
                        /* auto */ false,
index e332cdd..a510897 100644 (file)
@@ -542,7 +542,6 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                $ipbfields = [
                        'ipb_range_start' => '',
                        'ipb_range_end' => '',
-                       'ipb_by' => 0,
                        'ipb_timestamp' => $db->timestamp(),
                        'ipb_expiry' => $db->getInfinity(),
                ];
@@ -550,8 +549,6 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                        'rev_page' => 42,
                        'rev_text_id' => 42,
                        'rev_len' => 0,
-                       'rev_user' => 0,
-                       'rev_user_text' => '',
                        'rev_timestamp' => $db->timestamp(),
                ];
                $comStoreComment = new CommentStoreComment(
index 6fbe053..7fdc3ed 100644 (file)
@@ -50,6 +50,10 @@ class PageArchiveTest extends MediaWikiTestCase {
        protected function setUp() {
                parent::setUp();
 
+               $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->overrideMwServices();
+
                // First create our dummy page
                $page = Title::newFromText( 'PageArchiveTest_thePage' );
                $page = new WikiPage( $page );
@@ -84,28 +88,44 @@ class PageArchiveTest extends MediaWikiTestCase {
        public function testUndeleteRevisions() {
                // First make sure old revisions are archived
                $dbr = wfGetDB( DB_REPLICA );
-               $res = $dbr->select( 'archive', '*', [ 'ar_rev_id' => $this->ipRevId ] );
+               $arQuery = Revision::getArchiveQueryInfo();
+               $res = $dbr->select(
+                       $arQuery['tables'],
+                       $arQuery['fields'],
+                       [ 'ar_rev_id' => $this->ipRevId ],
+                       __METHOD__,
+                       [],
+                       $arQuery['joins']
+               );
                $row = $res->fetchObject();
                $this->assertEquals( $this->ipEditor, $row->ar_user_text );
 
                // Should not be in revision
-               $res = $dbr->select( 'revision', '*', [ 'rev_id' => $this->ipRevId ] );
+               $res = $dbr->select( 'revision', '1', [ 'rev_id' => $this->ipRevId ] );
                $this->assertFalse( $res->fetchObject() );
 
                // Should not be in ip_changes
-               $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $this->ipRevId ] );
+               $res = $dbr->select( 'ip_changes', '1', [ 'ipc_rev_id' => $this->ipRevId ] );
                $this->assertFalse( $res->fetchObject() );
 
                // Restore the page
                $this->archivedPage->undelete( [] );
 
                // Should be back in revision
-               $res = $dbr->select( 'revision', '*', [ 'rev_id' => $this->ipRevId ] );
+               $revQuery = Revision::getQueryInfo();
+               $res = $dbr->select(
+                       $revQuery['tables'],
+                       $revQuery['fields'],
+                       [ 'rev_id' => $this->ipRevId ],
+                       __METHOD__,
+                       [],
+                       $revQuery['joins']
+               );
                $row = $res->fetchObject();
                $this->assertEquals( $this->ipEditor, $row->rev_user_text );
 
                // Should be back in ip_changes
-               $res = $dbr->select( 'ip_changes', '*', [ 'ipc_rev_id' => $this->ipRevId ] );
+               $res = $dbr->select( 'ip_changes', [ 'ipc_hex' ], [ 'ipc_rev_id' => $this->ipRevId ] );
                $row = $res->fetchObject();
                $this->assertEquals( IP::toHex( $this->ipEditor ), $row->ipc_hex );
        }
@@ -134,6 +154,7 @@ class PageArchiveTest extends MediaWikiTestCase {
                                'ar_minor_edit' => '0',
                                'ar_user' => '0',
                                'ar_user_text' => '2600:387:ed7:947e:8c16:a1ad:dd34:1dd7',
+                               'ar_actor' => null,
                                'ar_len' => '11',
                                'ar_deleted' => '0',
                                'ar_rev_id' => '3',
@@ -159,6 +180,7 @@ class PageArchiveTest extends MediaWikiTestCase {
                                'ar_minor_edit' => '0',
                                'ar_user' => '0',
                                'ar_user_text' => '127.0.0.1',
+                               'ar_actor' => null,
                                'ar_len' => '7',
                                'ar_deleted' => '0',
                                'ar_rev_id' => '2',
index b05a742..61f1802 100644 (file)
@@ -108,7 +108,9 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                }
 
                if ( !isset( $props['user_text'] ) ) {
-                       $props['user_text'] = 'Tester';
+                       $user = $this->getTestUser()->getUser();
+                       $props['user_text'] = $user->getName();
+                       $props['user'] = $user->getId();
                }
 
                if ( !isset( $props['user'] ) ) {
@@ -243,7 +245,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                                'rev_id',
                                'rev_page',
                                'rev_text_id',
-                               'rev_user',
                                'rev_minor_edit',
                                'rev_deleted',
                                'rev_len',
@@ -257,7 +258,6 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                                strval( $textId ),
                                '0',
                                '0',
-                               '0',
                                '13',
                                strval( $parentId ),
                                's0ngbdoxagreuf2vjtuxzwdz64n29xm',
@@ -397,7 +397,8 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                        $services->getDBLoadBalancer(),
                        $services->getService( '_SqlBlobStore' ),
                        $services->getMainWANObjectCache(),
-                       $services->getCommentStore()
+                       $services->getCommentStore(),
+                       $services->getActorMigration()
                );
 
                $store->setContentHandlerUseDB( $this->getContentHandlerUseDB() );
@@ -745,15 +746,17 @@ abstract class RevisionDbTestBase extends MediaWikiTestCase {
                // test it ---------------------------------
                $since = $revisions[$sinceIdx]->getTimestamp();
 
+               $revQuery = Revision::getQueryInfo();
                $allRows = iterator_to_array( $dbw->select(
-                       'revision',
-                       [ 'rev_id', 'rev_timestamp', 'rev_user' ],
+                       $revQuery['tables'],
+                       [ 'rev_id', 'rev_timestamp', 'rev_user' => $revQuery['fields']['rev_user'] ],
                        [
                                'rev_page' => $page->getId(),
                                //'rev_timestamp > ' . $dbw->addQuotes( $dbw->timestamp( $since ) )
                        ],
                        __METHOD__,
-                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ]
+                       [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 50 ],
+                       $revQuery['joins']
                ) );
 
                $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since );
index 8eac064..8b644c5 100644 (file)
@@ -157,13 +157,6 @@ class RevisionTest extends MediaWikiTestCase {
                        new MWException( "Text already stored in external store (id someid), " .
                                "can't serialize content object" )
                ];
-               yield 'unknown user id and no user name' => [
-                       [
-                               'content' => new JavaScriptContent( 'hello world.' ),
-                               'user' => 9989,
-                       ],
-                       new MWException( 'user_text not given, and unknown user ID 9989' )
-               ];
                yield 'with bad content object (class)' => [
                        [ 'content' => new stdClass() ],
                        new MWException( 'content field must contain a Content object.' )
@@ -494,7 +487,8 @@ class RevisionTest extends MediaWikiTestCase {
                        $lb,
                        $this->getBlobStore(),
                        $cache,
-                       MediaWikiServices::getInstance()->getCommentStore()
+                       MediaWikiServices::getInstance()->getCommentStore(),
+                       MediaWikiServices::getInstance()->getActorMigration()
                );
                return $blobStore;
        }
@@ -617,6 +611,7 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testLoadFromTitle() {
                $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
                $this->overrideMwServices();
                $title = $this->getMockTitle();
 
@@ -875,6 +870,8 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testUserJoinCond() {
                $this->hideDeprecated( 'Revision::userJoinCond' );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->overrideMwServices();
                $this->assertEquals(
                        [ 'LEFT JOIN', [ 'rev_user != 0', 'user_id = rev_user' ] ],
                        Revision::userJoinCond()
@@ -892,7 +889,7 @@ class RevisionTest extends MediaWikiTestCase {
                );
        }
 
-       private function overrideCommentStore() {
+       private function overrideCommentStoreAndActorMigration() {
                $mockStore = $this->getMockBuilder( CommentStore::class )
                        ->disableOriginalConstructor()
                        ->getMock();
@@ -906,8 +903,26 @@ class RevisionTest extends MediaWikiTestCase {
                                'fields' => [ 'commentstore' => 'field' ],
                                'joins' => [ 'commentstore' => 'join' ],
                        ] );
-
                $this->setService( 'CommentStore', $mockStore );
+
+               $mockStore = $this->getMockBuilder( ActorMigration::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mockStore->expects( $this->any() )
+                       ->method( 'getJoin' )
+                       ->willReturnCallback( function ( $key ) {
+                               $p = strtok( $key, '_' );
+                               return [
+                                       'tables' => [ 'actormigration' => 'table' ],
+                                       'fields' => [
+                                               $p . '_user' => 'actormigration_user',
+                                               $p . '_user_text' => 'actormigration_user_text',
+                                               $p . '_actor' => 'actormigration_actor',
+                                       ],
+                                       'joins' => [ 'actormigration' => 'join' ],
+                               ];
+                       } );
+               $this->setService( 'ActorMigration', $mockStore );
        }
 
        public function provideSelectFields() {
@@ -920,6 +935,7 @@ class RevisionTest extends MediaWikiTestCase {
                                'rev_timestamp',
                                'rev_user_text',
                                'rev_user',
+                               'rev_actor' => 'NULL',
                                'rev_minor_edit',
                                'rev_deleted',
                                'rev_len',
@@ -939,6 +955,7 @@ class RevisionTest extends MediaWikiTestCase {
                                'rev_timestamp',
                                'rev_user_text',
                                'rev_user',
+                               'rev_actor' => 'NULL',
                                'rev_minor_edit',
                                'rev_deleted',
                                'rev_len',
@@ -956,7 +973,8 @@ class RevisionTest extends MediaWikiTestCase {
        public function testSelectFields( $contentHandlerUseDB, $expected ) {
                $this->hideDeprecated( 'Revision::selectFields' );
                $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
-               $this->overrideCommentStore();
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->overrideCommentStoreAndActorMigration();
                $this->assertEquals( $expected, Revision::selectFields() );
        }
 
@@ -972,6 +990,7 @@ class RevisionTest extends MediaWikiTestCase {
                                'ar_timestamp',
                                'ar_user_text',
                                'ar_user',
+                               'ar_actor' => 'NULL',
                                'ar_minor_edit',
                                'ar_deleted',
                                'ar_len',
@@ -993,6 +1012,7 @@ class RevisionTest extends MediaWikiTestCase {
                                'ar_timestamp',
                                'ar_user_text',
                                'ar_user',
+                               'ar_actor' => 'NULL',
                                'ar_minor_edit',
                                'ar_deleted',
                                'ar_len',
@@ -1010,7 +1030,8 @@ class RevisionTest extends MediaWikiTestCase {
        public function testSelectArchiveFields( $contentHandlerUseDB, $expected ) {
                $this->hideDeprecated( 'Revision::selectArchiveFields' );
                $this->setMwGlobals( 'wgContentHandlerUseDB', $contentHandlerUseDB );
-               $this->overrideCommentStore();
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->overrideCommentStoreAndActorMigration();
                $this->assertEquals( $expected, Revision::selectArchiveFields() );
        }
 
@@ -1068,6 +1089,7 @@ class RevisionTest extends MediaWikiTestCase {
                                'tables' => [
                                        'archive',
                                        'commentstore' => 'table',
+                                       'actormigration' => 'table',
                                ],
                                'fields' => [
                                        'ar_id',
@@ -1078,16 +1100,17 @@ class RevisionTest extends MediaWikiTestCase {
                                        'ar_text',
                                        'ar_text_id',
                                        'ar_timestamp',
-                                       'ar_user_text',
-                                       'ar_user',
                                        'ar_minor_edit',
                                        'ar_deleted',
                                        'ar_len',
                                        'ar_parent_id',
                                        'ar_sha1',
-                                       'commentstore' => 'field'
+                                       'commentstore' => 'field',
+                                       'ar_user' => 'actormigration_user',
+                                       'ar_user_text' => 'actormigration_user_text',
+                                       'ar_actor' => 'actormigration_actor',
                                ],
-                               'joins' => [ 'commentstore' => 'join' ],
+                               'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
                        ]
                ];
                yield 'wgContentHandlerUseDB true' => [
@@ -1098,6 +1121,7 @@ class RevisionTest extends MediaWikiTestCase {
                                'tables' => [
                                        'archive',
                                        'commentstore' => 'table',
+                                       'actormigration' => 'table',
                                ],
                                'fields' => [
                                        'ar_id',
@@ -1108,18 +1132,19 @@ class RevisionTest extends MediaWikiTestCase {
                                        'ar_text',
                                        'ar_text_id',
                                        'ar_timestamp',
-                                       'ar_user_text',
-                                       'ar_user',
                                        'ar_minor_edit',
                                        'ar_deleted',
                                        'ar_len',
                                        'ar_parent_id',
                                        'ar_sha1',
                                        'commentstore' => 'field',
+                                       'ar_user' => 'actormigration_user',
+                                       'ar_user_text' => 'actormigration_user_text',
+                                       'ar_actor' => 'actormigration_actor',
                                        'ar_content_format',
                                        'ar_content_model',
                                ],
-                               'joins' => [ 'commentstore' => 'join' ],
+                               'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
                        ]
                ];
        }
@@ -1130,7 +1155,7 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testGetArchiveQueryInfo( $globals, $expected ) {
                $this->setMwGlobals( $globals );
-               $this->overrideCommentStore();
+               $this->overrideCommentStoreAndActorMigration();
 
                $revisionStore = $this->getRevisionStore();
                $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
@@ -1148,22 +1173,23 @@ class RevisionTest extends MediaWikiTestCase {
                        ],
                        [],
                        [
-                               'tables' => [ 'revision', 'commentstore' => 'table' ],
+                               'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table' ],
                                'fields' => [
                                        'rev_id',
                                        'rev_page',
                                        'rev_text_id',
                                        'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
                                        'rev_minor_edit',
                                        'rev_deleted',
                                        'rev_len',
                                        'rev_parent_id',
                                        'rev_sha1',
                                        'commentstore' => 'field',
+                                       'rev_user' => 'actormigration_user',
+                                       'rev_user_text' => 'actormigration_user_text',
+                                       'rev_actor' => 'actormigration_actor',
                                ],
-                               'joins' => [ 'commentstore' => 'join' ],
+                               'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
                        ],
                ];
                yield 'wgContentHandlerUseDB false, opts page' => [
@@ -1172,20 +1198,21 @@ class RevisionTest extends MediaWikiTestCase {
                        ],
                        [ 'page' ],
                        [
-                               'tables' => [ 'revision', 'commentstore' => 'table', 'page' ],
+                               'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'page' ],
                                'fields' => [
                                        'rev_id',
                                        'rev_page',
                                        'rev_text_id',
                                        'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
                                        'rev_minor_edit',
                                        'rev_deleted',
                                        'rev_len',
                                        'rev_parent_id',
                                        'rev_sha1',
                                        'commentstore' => 'field',
+                                       'rev_user' => 'actormigration_user',
+                                       'rev_user_text' => 'actormigration_user_text',
+                                       'rev_actor' => 'actormigration_actor',
                                        'page_namespace',
                                        'page_title',
                                        'page_id',
@@ -1199,6 +1226,7 @@ class RevisionTest extends MediaWikiTestCase {
                                                [ 'page_id = rev_page' ],
                                        ],
                                        'commentstore' => 'join',
+                                       'actormigration' => 'join',
                                ],
                        ],
                ];
@@ -1208,31 +1236,33 @@ class RevisionTest extends MediaWikiTestCase {
                        ],
                        [ 'user' ],
                        [
-                               'tables' => [ 'revision', 'commentstore' => 'table', 'user' ],
+                               'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'user' ],
                                'fields' => [
                                        'rev_id',
                                        'rev_page',
                                        'rev_text_id',
                                        'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
                                        'rev_minor_edit',
                                        'rev_deleted',
                                        'rev_len',
                                        'rev_parent_id',
                                        'rev_sha1',
                                        'commentstore' => 'field',
+                                       'rev_user' => 'actormigration_user',
+                                       'rev_user_text' => 'actormigration_user_text',
+                                       'rev_actor' => 'actormigration_actor',
                                        'user_name',
                                ],
                                'joins' => [
                                        'user' => [
                                                'LEFT JOIN',
                                                [
-                                                       'rev_user != 0',
-                                                       'user_id = rev_user',
+                                                       'actormigration_user != 0',
+                                                       'user_id = actormigration_user',
                                                ],
                                        ],
                                        'commentstore' => 'join',
+                                       'actormigration' => 'join',
                                ],
                        ],
                ];
@@ -1242,20 +1272,21 @@ class RevisionTest extends MediaWikiTestCase {
                        ],
                        [ 'text' ],
                        [
-                               'tables' => [ 'revision', 'commentstore' => 'table', 'text' ],
+                               'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'text' ],
                                'fields' => [
                                        'rev_id',
                                        'rev_page',
                                        'rev_text_id',
                                        'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
                                        'rev_minor_edit',
                                        'rev_deleted',
                                        'rev_len',
                                        'rev_parent_id',
                                        'rev_sha1',
                                        'commentstore' => 'field',
+                                       'rev_user' => 'actormigration_user',
+                                       'rev_user_text' => 'actormigration_user_text',
+                                       'rev_actor' => 'actormigration_actor',
                                        'old_text',
                                        'old_flags',
                                ],
@@ -1265,6 +1296,7 @@ class RevisionTest extends MediaWikiTestCase {
                                                [ 'rev_text_id=old_id' ],
                                        ],
                                        'commentstore' => 'join',
+                                       'actormigration' => 'join',
                                ],
                        ],
                ];
@@ -1274,20 +1306,23 @@ class RevisionTest extends MediaWikiTestCase {
                        ],
                        [ 'text', 'page', 'user' ],
                        [
-                               'tables' => [ 'revision', 'commentstore' => 'table', 'page', 'user', 'text' ],
+                               'tables' => [
+                                       'revision', 'commentstore' => 'table', 'actormigration' => 'table', 'page', 'user', 'text'
+                               ],
                                'fields' => [
                                        'rev_id',
                                        'rev_page',
                                        'rev_text_id',
                                        'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
                                        'rev_minor_edit',
                                        'rev_deleted',
                                        'rev_len',
                                        'rev_parent_id',
                                        'rev_sha1',
                                        'commentstore' => 'field',
+                                       'rev_user' => 'actormigration_user',
+                                       'rev_user_text' => 'actormigration_user_text',
+                                       'rev_actor' => 'actormigration_actor',
                                        'page_namespace',
                                        'page_title',
                                        'page_id',
@@ -1306,8 +1341,8 @@ class RevisionTest extends MediaWikiTestCase {
                                        'user' => [
                                                'LEFT JOIN',
                                                [
-                                                       'rev_user != 0',
-                                                       'user_id = rev_user',
+                                                       'actormigration_user != 0',
+                                                       'user_id = actormigration_user',
                                                ],
                                        ],
                                        'text' => [
@@ -1315,6 +1350,7 @@ class RevisionTest extends MediaWikiTestCase {
                                                [ 'rev_text_id=old_id' ],
                                        ],
                                        'commentstore' => 'join',
+                                       'actormigration' => 'join',
                                ],
                        ],
                ];
@@ -1324,24 +1360,25 @@ class RevisionTest extends MediaWikiTestCase {
                        ],
                        [],
                        [
-                               'tables' => [ 'revision', 'commentstore' => 'table' ],
+                               'tables' => [ 'revision', 'commentstore' => 'table', 'actormigration' => 'table' ],
                                'fields' => [
                                        'rev_id',
                                        'rev_page',
                                        'rev_text_id',
                                        'rev_timestamp',
-                                       'rev_user_text',
-                                       'rev_user',
                                        'rev_minor_edit',
                                        'rev_deleted',
                                        'rev_len',
                                        'rev_parent_id',
                                        'rev_sha1',
                                        'commentstore' => 'field',
+                                       'rev_user' => 'actormigration_user',
+                                       'rev_user_text' => 'actormigration_user_text',
+                                       'rev_actor' => 'actormigration_actor',
                                        'rev_content_format',
                                        'rev_content_model',
                                ],
-                               'joins' => [ 'commentstore' => 'join' ],
+                               'joins' => [ 'commentstore' => 'join', 'actormigration' => 'join' ],
                        ],
                ];
        }
@@ -1352,7 +1389,7 @@ class RevisionTest extends MediaWikiTestCase {
         */
        public function testGetQueryInfo( $globals, $options, $expected ) {
                $this->setMwGlobals( $globals );
-               $this->overrideCommentStore();
+               $this->overrideCommentStoreAndActorMigration();
 
                $revisionStore = $this->getRevisionStore();
                $revisionStore->setContentHandlerUseDB( $globals['wgContentHandlerUseDB'] );
index d31ca5c..e81f0af 100644 (file)
@@ -134,6 +134,7 @@ class RevisionStoreDbTest extends MediaWikiTestCase {
                        $blobStore,
                        new WANObjectCache( [ 'cache' => new HashBagOStuff() ] ),
                        MediaWikiServices::getInstance()->getCommentStore(),
+                       MediaWikiServices::getInstance()->getActorMigration(),
                        $wikiId
                );
 
@@ -621,12 +622,15 @@ class RevisionStoreDbTest extends MediaWikiTestCase {
         * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
         */
        public function testNewRevisionFromRow_anonEdit() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
                $text = __METHOD__ . 'a-ä';
                /** @var Revision $rev */
                $rev = $page->doEditContent(
                        new WikitextContent( $text ),
-                       __METHOD__. 'a'
+                       __METHOD__ . 'a'
                )->value['revision'];
 
                $store = MediaWikiServices::getInstance()->getRevisionStore();
@@ -669,6 +673,9 @@ class RevisionStoreDbTest extends MediaWikiTestCase {
         * @covers \MediaWiki\Storage\RevisionStore::newRevisionFromRow_1_29
         */
        public function testNewRevisionFromRow_userEdit() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
                $text = __METHOD__ . 'b-ä';
                /** @var Revision $rev */
@@ -1048,7 +1055,7 @@ class RevisionStoreDbTest extends MediaWikiTestCase {
                $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
                /** @var Revision $rev */
                $rev = $page->doEditContent(
-                       new WikitextContent( __METHOD__. 'b' ),
+                       new WikitextContent( __METHOD__ . 'b' ),
                        __METHOD__ . 'b',
                        0,
                        false,
index aa59a5b..43784b6 100644 (file)
@@ -30,7 +30,7 @@ class RevisionStoreRecordTest extends MediaWikiTestCase {
                $title = Title::newFromText( 'Dummy' );
                $title->resetArticleID( 17 );
 
-               $user = new UserIdentityValue( 11, 'Tester' );
+               $user = new UserIdentityValue( 11, 'Tester', 0 );
                $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
 
                $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
@@ -58,7 +58,7 @@ class RevisionStoreRecordTest extends MediaWikiTestCase {
                $title = Title::newFromText( 'Dummy' );
                $title->resetArticleID( 17 );
 
-               $user = new UserIdentityValue( 11, 'Tester' );
+               $user = new UserIdentityValue( 11, 'Tester', 0 );
                $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
 
                $main = SlotRecord::newUnsaved( 'main', new TextContent( 'Lorem Ipsum' ) );
@@ -213,7 +213,7 @@ class RevisionStoreRecordTest extends MediaWikiTestCase {
                $title = Title::newFromText( 'Dummy' );
                $title->resetArticleID( 17 );
 
-               $user = new UserIdentityValue( 11, 'Tester' );
+               $user = new UserIdentityValue( 11, 'Tester', 0 );
 
                $comment = CommentStoreComment::newUnsavedComment( 'Hello World' );
 
@@ -688,7 +688,7 @@ class RevisionStoreRecordTest extends MediaWikiTestCase {
 
                        return new RevisionStoreRecord(
                                $title,
-                               new UserIdentityValue( 11, __METHOD__ ),
+                               new UserIdentityValue( 11, __METHOD__, 0 ),
                                CommentStoreComment::newUnsavedComment( __METHOD__ ),
                                (object)[
                                        'rev_id' => strval( $revId ),
index 8e8de6e..8498947 100644 (file)
@@ -32,7 +32,8 @@ class RevisionStoreTest extends MediaWikiTestCase {
                        $loadBalancer ? $loadBalancer : $this->getMockLoadBalancer(),
                        $blobStore ? $blobStore : $this->getMockSqlBlobStore(),
                        $WANObjectCache ? $WANObjectCache : $this->getHashWANObjectCache(),
-                       MediaWikiServices::getInstance()->getCommentStore()
+                       MediaWikiServices::getInstance()->getCommentStore(),
+                       MediaWikiServices::getInstance()->getActorMigration()
                );
        }
 
@@ -83,8 +84,6 @@ class RevisionStoreTest extends MediaWikiTestCase {
                        'rev_page',
                        'rev_text_id',
                        'rev_timestamp',
-                       'rev_user_text',
-                       'rev_user',
                        'rev_minor_edit',
                        'rev_deleted',
                        'rev_len',
@@ -101,6 +100,14 @@ class RevisionStoreTest extends MediaWikiTestCase {
                ];
        }
 
+       private function getActorQueryFields() {
+               return [
+                       'rev_user' => 'rev_user',
+                       'rev_user_text' => 'rev_user_text',
+                       'rev_actor' => 'NULL',
+               ];
+       }
+
        private function getContentHandlerQueryFields() {
                return [
                        'rev_content_format',
@@ -117,6 +124,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                'fields' => array_merge(
                                        $this->getDefaultQueryFields(),
                                        $this->getCommentQueryFields(),
+                                       $this->getActorQueryFields(),
                                        $this->getContentHandlerQueryFields()
                                ),
                                'joins' => [],
@@ -129,7 +137,8 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                'tables' => [ 'revision' ],
                                'fields' => array_merge(
                                        $this->getDefaultQueryFields(),
-                                       $this->getCommentQueryFields()
+                                       $this->getCommentQueryFields(),
+                                       $this->getActorQueryFields()
                                ),
                                'joins' => [],
                        ]
@@ -142,6 +151,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                'fields' => array_merge(
                                        $this->getDefaultQueryFields(),
                                        $this->getCommentQueryFields(),
+                                       $this->getActorQueryFields(),
                                        [
                                                'page_namespace',
                                                'page_title',
@@ -164,6 +174,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                'fields' => array_merge(
                                        $this->getDefaultQueryFields(),
                                        $this->getCommentQueryFields(),
+                                       $this->getActorQueryFields(),
                                        [
                                                'user_name',
                                        ]
@@ -181,6 +192,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                'fields' => array_merge(
                                        $this->getDefaultQueryFields(),
                                        $this->getCommentQueryFields(),
+                                       $this->getActorQueryFields(),
                                        [
                                                'old_text',
                                                'old_flags',
@@ -199,6 +211,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                'fields' => array_merge(
                                        $this->getDefaultQueryFields(),
                                        $this->getCommentQueryFields(),
+                                       $this->getActorQueryFields(),
                                        $this->getContentHandlerQueryFields(),
                                        [
                                                'page_namespace',
@@ -227,6 +240,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
         */
        public function testGetQueryInfo( $contentHandlerUseDb, $options, $expected ) {
                $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
                $this->overrideMwServices();
                $store = $this->getRevisionStore();
                $store->setContentHandlerUseDB( $contentHandlerUseDb );
@@ -243,8 +257,6 @@ class RevisionStoreTest extends MediaWikiTestCase {
                        'ar_text',
                        'ar_text_id',
                        'ar_timestamp',
-                       'ar_user_text',
-                       'ar_user',
                        'ar_minor_edit',
                        'ar_deleted',
                        'ar_len',
@@ -258,6 +270,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
         */
        public function testGetArchiveQueryInfo_contentHandlerDb() {
                $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
                $this->overrideMwServices();
                $store = $this->getRevisionStore();
                $store->setContentHandlerUseDB( true );
@@ -272,6 +285,9 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                                'ar_comment_text' => 'ar_comment',
                                                'ar_comment_data' => 'NULL',
                                                'ar_comment_cid' => 'NULL',
+                                               'ar_user_text' => 'ar_user_text',
+                                               'ar_user' => 'ar_user',
+                                               'ar_actor' => 'NULL',
                                                'ar_content_format',
                                                'ar_content_model',
                                        ]
@@ -287,6 +303,7 @@ class RevisionStoreTest extends MediaWikiTestCase {
         */
        public function testGetArchiveQueryInfo_noContentHandlerDb() {
                $this->setMwGlobals( 'wgCommentTableSchemaMigrationStage', MIGRATION_OLD );
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_OLD );
                $this->overrideMwServices();
                $store = $this->getRevisionStore();
                $store->setContentHandlerUseDB( false );
@@ -301,6 +318,9 @@ class RevisionStoreTest extends MediaWikiTestCase {
                                                'ar_comment_text' => 'ar_comment',
                                                'ar_comment_data' => 'NULL',
                                                'ar_comment_cid' => 'NULL',
+                                               'ar_user_text' => 'ar_user_text',
+                                               'ar_user' => 'ar_user',
+                                               'ar_actor' => 'NULL',
                                        ]
                                ),
                                'joins' => [],
index 575f0c9..3f6cac9 100644 (file)
@@ -157,6 +157,7 @@ class ApiBaseTest extends ApiTestCase {
                $block = new \Block( [
                        'address' => $user->getName(),
                        'user' => $user->getID(),
+                       'by' => $this->getTestSysop()->getUser()->getId(),
                        'reason' => __METHOD__,
                        'expiry' => time() + 100500,
                ] );
index 19f66fa..24b7500 100644 (file)
@@ -589,7 +589,7 @@ class ApiQueryRecentChangesIntegrationTest extends ApiTestCase {
                        'rc_minor' => 0,
                        'rc_cur_id' => $title->getArticleID(),
                        'rc_user' => 0,
-                       'rc_user_text' => 'External User',
+                       'rc_user_text' => 'm>External User',
                        'rc_comment' => '',
                        'rc_comment_text' => '',
                        'rc_comment_data' => null,
index fdbeded..8919c5e 100644 (file)
@@ -1072,7 +1072,7 @@ class ApiQueryWatchlistIntegrationTest extends ApiTestCase {
                        'rc_minor' => 0,
                        'rc_cur_id' => $title->getArticleID(),
                        'rc_user' => 0,
-                       'rc_user_text' => 'External User',
+                       'rc_user_text' => 'ext>External User',
                        'rc_comment' => '',
                        'rc_comment_text' => '',
                        'rc_comment_data' => null,
diff --git a/tests/phpunit/includes/api/query/ApiQueryUserContributionsTest.php b/tests/phpunit/includes/api/query/ApiQueryUserContributionsTest.php
new file mode 100644 (file)
index 0000000..6e8bc0b
--- /dev/null
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * @group API
+ * @group Database
+ * @group medium
+ * @covers ApiQueryContributions
+ */
+class ApiQueryContributionsTest extends ApiTestCase {
+       public function addDBDataOnce() {
+               global $wgActorTableSchemaMigrationStage;
+
+               $reset = new \Wikimedia\ScopedCallback( function ( $v ) {
+                       global $wgActorTableSchemaMigrationStage;
+                       $wgActorTableSchemaMigrationStage = $v;
+                       $this->overrideMwServices();
+               }, [ $wgActorTableSchemaMigrationStage ] );
+               $wgActorTableSchemaMigrationStage = MIGRATION_WRITE_BOTH;
+               $this->overrideMwServices();
+
+               $users = [
+                       User::newFromName( '192.168.2.2', false ),
+                       User::newFromName( '192.168.2.1', false ),
+                       User::newFromName( '192.168.2.3', false ),
+                       User::createNew( __CLASS__ . ' B' ),
+                       User::createNew( __CLASS__ . ' A' ),
+                       User::createNew( __CLASS__ . ' C' ),
+               ];
+
+               $title = Title::newFromText( __CLASS__ );
+               $page = WikiPage::factory( $title );
+               for ( $i = 0; $i < 3; $i++ ) {
+                       foreach ( array_reverse( $users ) as $user ) {
+                               $status = $page->doEditContent(
+                                       ContentHandler::makeContent( "Test revision $user #$i", $title ), 'Test edit', 0, false, $user
+                               );
+                               if ( !$status->isOK() ) {
+                                       $this->fail( "Failed to edit $title: " . $status->getWikiText( false, false, 'en' ) );
+                               }
+                       }
+               }
+       }
+
+       /**
+        * @dataProvider provideSorting
+        * @param int $stage One of the MIGRATION_* constants for $wgActorTableSchemaMigrationStage
+        * @param array $params Extra parameters for the query
+        * @param bool $reverse Reverse order?
+        * @param int $revs Number of revisions to expect
+        */
+       public function testSorting( $stage, $params, $reverse, $revs ) {
+               if ( isset( $params['ucuserprefix'] ) &&
+                       ( $stage === MIGRATION_WRITE_BOTH || $stage === MIGRATION_WRITE_NEW ) &&
+                       $this->db->getType() === 'mysql' && $this->usesTemporaryTables()
+               ) {
+                       // https://bugs.mysql.com/bug.php?id=10327
+                       $this->markTestSkipped( 'MySQL bug 10327 - can\'t reopen temporary tables' );
+               }
+
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', $stage );
+               $this->overrideMwServices();
+
+               if ( isset( $params['ucuserids'] ) ) {
+                       $params['ucuserids'] = implode( '|', array_map( 'User::idFromName', $params['ucuserids'] ) );
+               }
+               if ( isset( $params['ucuser'] ) ) {
+                       $params['ucuser'] = implode( '|', $params['ucuser'] );
+               }
+
+               $sort = 'rsort';
+               if ( $reverse ) {
+                       $params['ucdir'] = 'newer';
+                       $sort = 'sort';
+               }
+
+               $params += [
+                       'action' => 'query',
+                       'list' => 'usercontribs',
+                       'ucprop' => 'ids',
+               ];
+
+               $apiResult = $this->doApiRequest( $params + [ 'uclimit' => 500 ] );
+               $this->assertArrayNotHasKey( 'continue', $apiResult[0] );
+               $this->assertArrayHasKey( 'query', $apiResult[0] );
+               $this->assertArrayHasKey( 'usercontribs', $apiResult[0]['query'] );
+
+               $count = 0;
+               $ids = [];
+               foreach ( $apiResult[0]['query']['usercontribs'] as $page ) {
+                       $count++;
+                       $ids[$page['user']][] = $page['revid'];
+               }
+               $this->assertSame( $revs, $count, 'Expected number of revisions' );
+               foreach ( $ids as $user => $revids ) {
+                       $sorted = $revids;
+                       call_user_func_array( $sort, [ &$sorted ] );
+                       $this->assertSame( $sorted, $revids, "IDs for $user are sorted" );
+               }
+
+               for ( $limit = 1; $limit < $revs; $limit++ ) {
+                       $continue = [];
+                       $count = 0;
+                       $batchedIds = [];
+                       while ( $continue !== null ) {
+                               $apiResult = $this->doApiRequest( $params + [ 'uclimit' => $limit ] + $continue );
+                               $this->assertArrayHasKey( 'query', $apiResult[0], "Batching with limit $limit" );
+                               $this->assertArrayHasKey( 'usercontribs', $apiResult[0]['query'],
+                                       "Batching with limit $limit" );
+                               $continue = isset( $apiResult[0]['continue'] ) ? $apiResult[0]['continue'] : null;
+                               foreach ( $apiResult[0]['query']['usercontribs'] as $page ) {
+                                       $count++;
+                                       $batchedIds[$page['user']][] = $page['revid'];
+                               }
+                               $this->assertLessThanOrEqual( $revs, $count, "Batching with limit $limit" );
+                       }
+                       $this->assertSame( $ids, $batchedIds, "Result set is the same when batching with limit $limit" );
+               }
+       }
+
+       public static function provideSorting() {
+               $users = [ __CLASS__ . ' A', __CLASS__ . ' B', __CLASS__ . ' C' ];
+               $users2 = [ __CLASS__ . ' A', __CLASS__ . ' B', __CLASS__ . ' D' ];
+               $ips = [ '192.168.2.1', '192.168.2.2', '192.168.2.3', '192.168.2.4' ];
+
+               foreach (
+                       [
+                               'old' => MIGRATION_OLD,
+                               'write both' => MIGRATION_WRITE_BOTH,
+                               'write new' => MIGRATION_WRITE_NEW,
+                               'new' => MIGRATION_NEW,
+                       ] as $stageName => $stage
+               ) {
+                       foreach ( [ false, true ] as $reverse ) {
+                               $name = $stageName . ( $reverse ? ', reverse' : '' );
+                               yield "Named users, $name" => [ $stage, [ 'ucuser' => $users ], $reverse, 9 ];
+                               yield "Named users including a no-edit user, $name" => [
+                                       $stage, [ 'ucuser' => $users2 ], $reverse, 6
+                               ];
+                               yield "IP users, $name" => [ $stage, [ 'ucuser' => $ips ], $reverse, 9 ];
+                               yield "All users, $name" => [
+                                       $stage, [ 'ucuser' => array_merge( $users, $ips ) ], $reverse, 18
+                               ];
+                               yield "User IDs, $name" => [ $stage, [ 'ucuserids' => $users ], $reverse, 9 ];
+                               yield "Users by prefix, $name" => [ $stage, [ 'ucuserprefix' => __CLASS__ ], $reverse, 9 ];
+                               yield "IPs by prefix, $name" => [ $stage, [ 'ucuserprefix' => '192.168.2.' ], $reverse, 9 ];
+                       }
+               }
+       }
+}
index e4056ee..211eba0 100644 (file)
@@ -1436,6 +1436,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
                $blockOptions = [
                        'address' => 'UTBlockee',
                        'user' => $user->getID(),
+                       'by' => $this->getTestSysop()->getUser()->getId(),
                        'reason' => __METHOD__,
                        'expiry' => time() + 100500,
                        'createAccount' => true,
@@ -1448,6 +1449,7 @@ class AuthManagerTest extends \MediaWikiTestCase {
 
                $blockOptions = [
                        'address' => '127.0.0.0/24',
+                       'by' => $this->getTestSysop()->getUser()->getId(),
                        'reason' => __METHOD__,
                        'expiry' => time() + 100500,
                        'createAccount' => true,
index 4e44547..81cdc9d 100644 (file)
@@ -76,6 +76,7 @@ class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase
                $blockOptions = [
                        'address' => 'UTBlockee',
                        'user' => $user->getID(),
+                       'by' => $this->getTestSysop()->getUser()->getId(),
                        'reason' => __METHOD__,
                        'expiry' => time() + 100500,
                        'createAccount' => true,
@@ -149,6 +150,7 @@ class CheckBlocksSecondaryAuthenticationProviderTest extends \MediaWikiTestCase
                $blockOptions = [
                        'address' => '127.0.0.0/24',
                        'reason' => __METHOD__,
+                       'by' => $this->getTestSysop()->getUser()->getId(),
                        'expiry' => time() + 100500,
                        'createAccount' => true,
                ];
index ab42696..333eb28 100644 (file)
@@ -27,12 +27,16 @@ class RecentChangeTest extends MediaWikiTestCase {
         * @covers RecentChange::loadFromRow
         */
        public function testNewFromRow() {
+               $user = $this->getTestUser()->getUser();
+               $actorId = $user->getActorId();
+
                $row = new stdClass();
                $row->rc_foo = 'AAA';
                $row->rc_timestamp = '20150921134808';
                $row->rc_deleted = 'bar';
                $row->rc_comment_text = 'comment';
                $row->rc_comment_data = null;
+               $row->rc_user = $user->getId();
 
                $rc = RecentChange::newFromRow( $row );
 
@@ -43,6 +47,9 @@ class RecentChangeTest extends MediaWikiTestCase {
                        'rc_comment' => 'comment',
                        'rc_comment_text' => 'comment',
                        'rc_comment_data' => null,
+                       'rc_user' => $user->getId(),
+                       'rc_user_text' => $user->getName(),
+                       'rc_actor' => $actorId,
                ];
                $this->assertEquals( $expected, $rc->getAttributes() );
 
@@ -51,6 +58,7 @@ class RecentChangeTest extends MediaWikiTestCase {
                $row->rc_timestamp = '20150921134808';
                $row->rc_deleted = 'bar';
                $row->rc_comment = 'comment';
+               $row->rc_user = $user->getId();
 
                Wikimedia\suppressWarnings();
                $rc = RecentChange::newFromRow( $row );
@@ -63,6 +71,9 @@ class RecentChangeTest extends MediaWikiTestCase {
                        'rc_comment' => 'comment',
                        'rc_comment_text' => 'comment',
                        'rc_comment_data' => null,
+                       'rc_user' => $user->getId(),
+                       'rc_user_text' => $user->getName(),
+                       'rc_actor' => $actorId,
                ];
                $this->assertEquals( $expected, $rc->getAttributes() );
        }
index 7bb03db..3b91f5b 100644 (file)
@@ -290,12 +290,15 @@ EOF
                $importer->doImport();
 
                $db = wfGetDB( DB_MASTER );
+               $revQuery = Revision::getQueryInfo();
 
                $row = $db->selectRow(
-                       'revision',
-                       [ 'rev_user', 'rev_user_text' ],
+                       $revQuery['tables'],
+                       $revQuery['fields'],
                        [ 'rev_timestamp' => $db->timestamp( "201601010{$n}0000" ) ],
-                       __METHOD__
+                       __METHOD__,
+                       [],
+                       $revQuery['joins']
                );
                $this->assertSame(
                        $assign && $create ? 'UserDoesNotExist' : 'Xxx>UserDoesNotExist',
@@ -304,10 +307,12 @@ EOF
                $this->assertSame( $assign && $create ? $hookId : 0, (int)$row->rev_user );
 
                $row = $db->selectRow(
-                       'revision',
-                       [ 'rev_user', 'rev_user_text' ],
+                       $revQuery['tables'],
+                       $revQuery['fields'],
                        [ 'rev_timestamp' => $db->timestamp( "201601010{$n}0001" ) ],
-                       __METHOD__
+                       __METHOD__,
+                       [],
+                       $revQuery['joins']
                );
                $this->assertSame( ( $assign ? '' : 'Xxx>' ) . $user->getName(), $row->rev_user_text );
                $this->assertSame( $assign ? $user->getId() : 0, (int)$row->rev_user );
index 89cf68f..667eb0a 100644 (file)
@@ -149,6 +149,12 @@ class CSSMinTest extends MediaWikiTestCase {
                        [ "foo { content: '\"'; }", "foo{content:'\"'}" ],
                        // - Whitespace in string values
                        [ 'foo { content: " "; }', 'foo{content:" "}' ],
+
+                       // Whitespaces after opening and before closing parentheses and brackets
+                       [ 'a:not( [ href ] ) { prop: url( foobar.png ); }', 'a:not([href]){prop:url(foobar.png)}' ],
+
+                       // Ensure that the invalid "url (" will not become the valid "url(" by minification
+                       [ 'foo { prop: url ( foobar.png ); }', 'foo{prop:url (foobar.png)}' ],
                ];
        }
 
index b9f57b5..5fcca1a 100644 (file)
@@ -495,4 +495,19 @@ class DatabaseMysqlBaseTest extends PHPUnit\Framework\TestCase {
 
                $db->clearFlag( Database::DBO_IGNORE );
        }
+
+       /**
+        * @covers Wikimedia\Rdbms\MySQLMasterPos
+        */
+       public function testSerialize() {
+               $pos = new MySQLMasterPos( '3E11FA47-71CA-11E1-9E33-C80AA9429562:99', 53636363 );
+               $roundtripPos = unserialize( serialize( $pos ) );
+
+               $this->assertEquals( $pos, $roundtripPos );
+
+               $pos = new MySQLMasterPos( '255-11-23', 53636363 );
+               $roundtripPos = unserialize( serialize( $pos ) );
+
+               $this->assertEquals( $pos, $roundtripPos );
+       }
 }
index 2dc9a2c..786d761 100644 (file)
@@ -36,6 +36,7 @@ abstract class LogFormatterTestCase extends MediaWikiLangTestCase {
                        'log_timestamp' => isset( $data['timestamp'] ) ? $data['timestamp'] : wfTimestampNow(),
                        'log_user' => isset( $data['user'] ) ? $data['user'] : 0,
                        'log_user_text' => isset( $data['user_text'] ) ? $data['user_text'] : 'User',
+                       'log_actor' => isset( $data['actor'] ) ? $data['actor'] : 0,
                        'log_namespace' => isset( $data['namespace'] ) ? $data['namespace'] : NS_MAIN,
                        'log_title' => isset( $data['title'] ) ? $data['title'] : 'Main_Page',
                        'log_page' => isset( $data['page'] ) ? $data['page'] : 0,
index 9792172..c943cef 100644 (file)
@@ -110,8 +110,19 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
        }
 
        public function testInfiniteRead() {
+               // test file truncated right after a segment, which previously
+               // caused an infinite loop looking for the next segment byte.
                // Should get past infinite loop and throw in wfUnpack()
                $this->setExpectedException( 'MWException' );
-               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-loop.jpg' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop1.jpg' );
+       }
+
+       public function testInfiniteRead2() {
+               // test file truncated after a segment's marker and size, which
+               // would cause a seek past end of file. Seek past end of file
+               // doesn't actually fail, but prevents further reading and was
+               // devolving into the previous case (testInfiniteRead).
+               $this->setExpectedException( 'MWException' );
+               $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-segment-loop2.jpg' );
        }
 }
index 6b680e5..7adc43b 100644 (file)
@@ -1784,12 +1784,13 @@ more stuff
 
                // Make sure the log entry looks good
                // log_params is not checked here
+               $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' );
                $this->assertSelect(
-                       'logging',
+                       [ 'logging' ] + $actorQuery['tables'],
                        [
                                'log_comment',
-                               'log_user',
-                               'log_user_text',
+                               'log_user' => $actorQuery['fields']['log_user'],
+                               'log_user_text' => $actorQuery['fields']['log_user_text'],
                                'log_namespace',
                                'log_title',
                        ],
@@ -1800,7 +1801,9 @@ more stuff
                                $user->getName(),
                                (string)$page->getTitle()->getNamespace(),
                                $page->getTitle()->getDBkey(),
-                       ] ]
+                       ] ],
+                       [],
+                       $actorQuery['joins']
                );
        }
 
index 0839cfb..78175fa 100644 (file)
@@ -26,6 +26,8 @@
  */
 class UserPasswordPolicyTest extends MediaWikiTestCase {
 
+       protected $tablesUsed = [ 'user', 'user_groups' ];
+
        protected $policies = [
                'checkuser' => [
                        'MinimalPasswordLength' => 10,
index 3862cc2..2e03163 100644 (file)
@@ -170,5 +170,12 @@ class CommandTest extends PHPUnit\Framework\TestCase {
                $command->input( str_repeat( '!', 1000000 ) );
                $result = $command->execute();
                $this->assertSame( 1000000, strlen( $result->getStdout() ) );
+
+               // And try it with empty input
+               $command = new Command();
+               $command->params( 'cat' );
+               $command->input( '' );
+               $result = $command->execute();
+               $this->assertSame( '', $result->getStdout() );
        }
 }
index 9b81d6d..aac25d8 100644 (file)
@@ -199,10 +199,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testRcHidemyselfFilter() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $user = $this->getTestUser()->getUser();
+               $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "rc_user_text != '{$user->getName()}'",
+                               "NOT((rc_actor = '{$user->getActorId()}') OR "
+                                       . "(rc_actor = '0' AND rc_user = '{$user->getId()}'))",
                        ],
                        [
                                'hidemyself' => 1,
@@ -212,9 +217,10 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
 
                $user = User::newFromName( '10.11.12.13', false );
+               $id = $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "rc_user_text != '10.11.12.13'",
+                               "NOT((rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13'))",
                        ],
                        [
                                'hidemyself' => 1,
@@ -225,10 +231,15 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testRcHidebyothersFilter() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $user = $this->getTestUser()->getUser();
+               $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "rc_user_text = '{$user->getName()}'",
+                               "(rc_actor = '{$user->getActorId()}') OR "
+                               . "(rc_actor = '0' AND rc_user_text = '{$user->getName()}')",
                        ],
                        [
                                'hidebyothers' => 1,
@@ -238,9 +249,10 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                );
 
                $user = User::newFromName( '10.11.12.13', false );
+               $id = $user->getActorId( wfGetDB( DB_MASTER ) );
                $this->assertConditions(
                        [ # expected
-                               "rc_user_text = '10.11.12.13'",
+                               "(rc_actor = '$id') OR (rc_actor = '0' AND rc_user_text = '10.11.12.13')",
                        ],
                        [
                                'hidebyothers' => 1,
@@ -428,10 +440,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelAllExperienceLevels() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $this->assertConditions(
                        [
                                # expected
-                               'rc_user != 0',
+                               'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
                        ],
                        [
                                'userExpLevel' => 'newcomer;learner;experienced',
@@ -441,10 +456,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelRegistrered() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $this->assertConditions(
                        [
                                # expected
-                               'rc_user != 0',
+                               'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
                        ],
                        [
                                'userExpLevel' => 'registered',
@@ -454,10 +472,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelUnregistrered() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $this->assertConditions(
                        [
                                # expected
-                               'rc_user' => 0,
+                               'COALESCE( actor_rc_user.actor_user, rc_user ) = 0',
                        ],
                        [
                                'userExpLevel' => 'unregistered',
@@ -467,10 +488,13 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelRegistreredOrLearner() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $this->assertConditions(
                        [
                                # expected
-                               'rc_user != 0',
+                               'COALESCE( actor_rc_user.actor_user, rc_user ) != 0',
                        ],
                        [
                                'userExpLevel' => 'registered;learner',
@@ -480,10 +504,14 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
        }
 
        public function testFilterUserExpLevelUnregistreredOrExperienced() {
+               $this->setMwGlobals( 'wgActorTableSchemaMigrationStage', MIGRATION_WRITE_BOTH );
+               $this->overrideMwServices();
+
                $conds = $this->buildQuery( [ 'userExpLevel' => 'unregistered;experienced' ] );
 
                $this->assertRegExp(
-                       '/\(rc_user = 0\) OR \(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
+                       '/\(COALESCE\( actor_rc_user.actor_user, rc_user \) = 0\) OR '
+                               . '\(\(user_editcount >= 500\) AND \(user_registration <= \'[^\']+\'\)\)/',
                        reset( $conds ),
                        "rc conditions: userExpLevel=unregistered;experienced"
                );
@@ -595,8 +623,10 @@ class ChangesListSpecialPageTest extends AbstractChangesListSpecialPageTestCase
                        ]
                );
 
+               // @todo: This is not at all safe or sane. It just blindly assumes
+               // nothing in $conds depends on any other tables.
                $result = wfGetDB( DB_MASTER )->select(
-                       $tables,
+                       'user',
                        'user_name',
                        array_filter( $conds ) + [ 'user_email' => 'ut' ]
                );
index f95e387..4862747 100644 (file)
@@ -4,6 +4,9 @@
  * @group Database
  */
 class UserGroupMembershipTest extends MediaWikiTestCase {
+
+       protected $tablesUsed = [ 'user', 'user_groups' ];
+
        /**
         * @var User Belongs to no groups
         */
index 4c1a5fd..c225ba5 100644 (file)
@@ -21,7 +21,9 @@ class UserTest extends MediaWikiTestCase {
                $this->setMwGlobals( [
                        'wgGroupPermissions' => [],
                        'wgRevokePermissions' => [],
+                       'wgActorTableSchemaMigrationStage' => MIGRATION_WRITE_BOTH,
                ] );
+               $this->overrideMwServices();
 
                $this->setUpPermissionGlobals();
 
@@ -617,6 +619,7 @@ class UserTest extends MediaWikiTestCase {
                        'enableAutoblock' => true,
                        'expiry' => wfTimestamp( TS_MW, $expiryFiveHours ),
                ] );
+               $block->setBlocker( $this->getTestSysop()->getUser() );
                $block->setTarget( $user1tmp );
                $block->setBlocker( $userBlocker );
                $res = $block->insert();
@@ -694,6 +697,7 @@ class UserTest extends MediaWikiTestCase {
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $testUser );
                $block = new Block( [ 'enableAutoblock' => true ] );
+               $block->setBlocker( $this->getTestSysop()->getUser() );
                $block->setTarget( $testUser );
                $block->setBlocker( $userBlocker );
                $res = $block->insert();
@@ -739,6 +743,7 @@ class UserTest extends MediaWikiTestCase {
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $user1Tmp );
                $block = new Block( [ 'enableAutoblock' => true, 'expiry' => 'infinity' ] );
+               $block->setBlocker( $this->getTestSysop()->getUser() );
                $block->setTarget( $user1Tmp );
                $block->setBlocker( $userBlocker );
                $res = $block->insert();
@@ -834,6 +839,7 @@ class UserTest extends MediaWikiTestCase {
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $user1tmp );
                $block = new Block( [ 'enableAutoblock' => true ] );
+               $block->setBlocker( $this->getTestSysop()->getUser() );
                $block->setTarget( $user1tmp );
                $block->setBlocker( $userBlocker );
                $res = $block->insert();
@@ -879,6 +885,7 @@ class UserTest extends MediaWikiTestCase {
                $request1 = new FauxRequest();
                $request1->getSession()->setUser( $user1tmp );
                $block = new Block( [ 'enableAutoblock' => true ] );
+               $block->setBlocker( $this->getTestSysop()->getUser() );
                $block->setTarget( $user1tmp );
                $block->setBlocker( $userBlocker );
                $res = $block->insert();
@@ -956,20 +963,18 @@ class UserTest extends MediaWikiTestCase {
                ] );
 
                $db = wfGetDB( DB_MASTER );
-
-               $data = new stdClass();
-               $data->user_id = 1;
-               $data->user_name = 'name';
-               $data->user_real_name = 'Real Name';
-               $data->user_touched = 1;
-               $data->user_token = 'token';
-               $data->user_email = 'a@a.a';
-               $data->user_email_authenticated = null;
-               $data->user_email_token = 'token';
-               $data->user_email_token_expires = null;
-               $data->user_editcount = $editCount;
-               $data->user_registration = $db->timestamp( time() - $memberSince * 86400 );
-               $user = User::newFromRow( $data );
+               $userQuery = User::getQueryInfo();
+               $row = $db->selectRow(
+                       $userQuery['tables'],
+                       $userQuery['fields'],
+                       [ 'user_id' => $this->getTestUser()->getUser()->getId() ],
+                       __METHOD__,
+                       [],
+                       $userQuery['joins']
+               );
+               $row->user_editcount = $editCount;
+               $row->user_registration = $db->timestamp( time() - $memberSince * 86400 );
+               $user = User::newFromRow( $row );
 
                $this->assertEquals( $expLevel, $user->getExperienceLevel() );
        }
@@ -1028,4 +1033,113 @@ class UserTest extends MediaWikiTestCase {
                );
                $this->assertTrue( User::isLocallyBlockedProxy( $ip ) );
        }
+
+       public function testActorId() {
+               $this->hideDeprecated( 'User::selectFields' );
+
+               // Newly-created user has an actor ID
+               $user = User::createNew( 'UserTestActorId1' );
+               $id = $user->getId();
+               $this->assertTrue( $user->getActorId() > 0, 'User::createNew sets an actor ID' );
+
+               $user = User::newFromName( 'UserTestActorId2' );
+               $user->addToDatabase();
+               $this->assertTrue( $user->getActorId() > 0, 'User::addToDatabase sets an actor ID' );
+
+               $user = User::newFromName( 'UserTestActorId1' );
+               $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by name' );
+
+               $user = User::newFromId( $id );
+               $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be retrieved for user loaded by ID' );
+
+               $user2 = User::newFromActorId( $user->getActorId() );
+               $this->assertEquals( $user->getId(), $user2->getId(),
+                       'User::newFromActorId works for an existing user' );
+
+               $row = $this->db->selectRow( 'user', User::selectFields(), [ 'user_id' => $id ], __METHOD__ );
+               $user = User::newFromRow( $row );
+               $this->assertTrue( $user->getActorId() > 0,
+                       'Actor ID can be retrieved for user loaded with User::selectFields()' );
+
+               $this->db->delete( 'actor', [ 'actor_user' => $id ], __METHOD__ );
+               User::purge( wfWikiId(), $id );
+               // Because WANObjectCache->delete() stupidly doesn't delete from the process cache.
+               ObjectCache::getMainWANInstance()->clearProcessCache();
+
+               $user = User::newFromId( $id );
+               $this->assertFalse( $user->getActorId() > 0, 'No Actor ID by default if none in database' );
+               $this->assertTrue( $user->getActorId( $this->db ) > 0, 'Actor ID can be created if none in db' );
+
+               $user->setName( 'UserTestActorId4-renamed' );
+               $user->saveSettings();
+               $this->assertEquals(
+                       $user->getName(),
+                       $this->db->selectField(
+                               'actor', 'actor_name', [ 'actor_id' => $user->getActorId() ], __METHOD__
+                       ),
+                       'User::saveSettings updates actor table for name change'
+               );
+
+               // For sanity
+               $ip = '192.168.12.34';
+               $this->db->delete( 'actor', [ 'actor_name' => $ip ], __METHOD__ );
+
+               $user = User::newFromName( $ip, false );
+               $this->assertFalse( $user->getActorId() > 0, 'Anonymous user has no actor ID by default' );
+               $this->assertTrue( $user->getActorId( $this->db ) > 0,
+                       'Actor ID can be created for an anonymous user' );
+
+               $user = User::newFromName( $ip, false );
+               $this->assertTrue( $user->getActorId() > 0, 'Actor ID can be loaded for an anonymous user' );
+               $user2 = User::newFromActorId( $user->getActorId() );
+               $this->assertEquals( $user->getName(), $user2->getName(),
+                       'User::newFromActorId works for an anonymous user' );
+       }
+
+       public function testNewFromAnyId() {
+               // Registered user
+               $user = $this->getTestUser()->getUser();
+               for ( $i = 1; $i <= 7; $i++ ) {
+                       $test = User::newFromAnyId(
+                               ( $i & 1 ) ? $user->getId() : null,
+                               ( $i & 2 ) ? $user->getName() : null,
+                               ( $i & 4 ) ? $user->getActorId() : null
+                       );
+                       $this->assertSame( $user->getId(), $test->getId() );
+                       $this->assertSame( $user->getName(), $test->getName() );
+                       $this->assertSame( $user->getActorId(), $test->getActorId() );
+               }
+
+               // Anon user. Can't load by only user ID when that's 0.
+               $user = User::newFromName( '192.168.12.34', false );
+               $user->getActorId( $this->db ); // Make sure an actor ID exists
+
+               $test = User::newFromAnyId( null, '192.168.12.34', null );
+               $this->assertSame( $user->getId(), $test->getId() );
+               $this->assertSame( $user->getName(), $test->getName() );
+               $this->assertSame( $user->getActorId(), $test->getActorId() );
+               $test = User::newFromAnyId( null, null, $user->getActorId() );
+               $this->assertSame( $user->getId(), $test->getId() );
+               $this->assertSame( $user->getName(), $test->getName() );
+               $this->assertSame( $user->getActorId(), $test->getActorId() );
+
+               // Bogus data should still "work" as long as nothing triggers a ->load(),
+               // and accessing the specified data shouldn't do that.
+               $test = User::newFromAnyId( 123456, 'Bogus', 654321 );
+               $this->assertSame( 123456, $test->getId() );
+               $this->assertSame( 'Bogus', $test->getName() );
+               $this->assertSame( 654321, $test->getActorId() );
+
+               // Exceptional cases
+               try {
+                       User::newFromAnyId( null, null, null );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+               }
+               try {
+                       User::newFromAnyId( 0, null, 0 );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( InvalidArgumentException $ex ) {
+               }
+       }
 }
index b6cf239..be51626 100644 (file)
@@ -30,6 +30,40 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                return $mockStore;
        }
 
+       /**
+        * @return PHPUnit_Framework_MockObject_MockObject|ActorMigration
+        */
+       private function getMockActorMigration() {
+               $mockStore = $this->getMockBuilder( ActorMigration::class )
+                       ->disableOriginalConstructor()
+                       ->getMock();
+               $mockStore->expects( $this->any() )
+                       ->method( 'getJoin' )
+                       ->willReturn( [
+                               'tables' => [ 'actormigration' => 'table' ],
+                               'fields' => [
+                                       'rc_user' => 'actormigration_user',
+                                       'rc_user_text' => 'actormigration_user_text',
+                                       'rc_actor' => 'actormigration_actor',
+                               ],
+                               'joins' => [ 'actormigration' => 'join' ],
+                       ] );
+               $mockStore->expects( $this->any() )
+                       ->method( 'getWhere' )
+                       ->willReturn( [
+                               'tables' => [ 'actormigration' => 'table' ],
+                               'conds' => 'actormigration_conds',
+                               'joins' => [ 'actormigration' => 'join' ],
+                       ] );
+               $mockStore->expects( $this->any() )
+                       ->method( 'isAnon' )
+                       ->willReturn( 'actormigration is anon' );
+               $mockStore->expects( $this->any() )
+                       ->method( 'isNotAnon' )
+                       ->willReturn( 'actormigration is not anon' );
+               return $mockStore;
+       }
+
        /**
         * @param PHPUnit_Framework_MockObject_MockObject|Database $mockDb
         * @return WatchedItemQueryService
@@ -37,7 +71,8 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
        private function newService( $mockDb ) {
                return new WatchedItemQueryService(
                        $this->getMockLoadBalancer( $mockDb ),
-                       $this->getMockCommentStore()
+                       $this->getMockCommentStore(),
+                       $this->getMockActorMigration()
                );
        }
 
@@ -57,10 +92,17 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                        )
                        ->will( $this->returnCallback( function ( $a, $conj ) {
                                $sqlConj = $conj === LIST_AND ? ' AND ' : ' OR ';
-                               return implode( $sqlConj, array_map( function ( $s ) {
-                                       return '(' . $s . ')';
-                               }, $a
-                               ) );
+                               $conds = [];
+                               foreach ( $a as $k => $v ) {
+                                       if ( is_int( $k ) ) {
+                                               $conds[] = "($v)";
+                                       } elseif ( is_array( $v ) ) {
+                                               $conds[] = "($k IN ('" . implode( "','", $v ) . "'))";
+                                       } else {
+                                               $conds[] = "($k = '$v')";
+                                       }
+                               }
+                               return implode( $sqlConj, $conds );
                        } ) );
 
                $mock->expects( $this->any() )
@@ -490,20 +532,20 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER ] ],
                                null,
-                               [],
-                               [ 'rc_user_text' ],
-                               [],
+                               [ 'actormigration' => 'table' ],
+                               [ 'rc_user_text' => 'actormigration_user_text' ],
                                [],
                                [],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_USER_ID ] ],
                                null,
-                               [],
-                               [ 'rc_user' ],
-                               [],
+                               [ 'actormigration' => 'table' ],
+                               [ 'rc_user' => 'actormigration_user' ],
                                [],
                                [],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'includeFields' => [ WatchedItemQueryService::INCLUDE_COMMENT ] ],
@@ -705,20 +747,20 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_ANON ] ],
                                null,
+                               [ 'actormigration' => 'table' ],
                                [],
+                               [ 'actormigration is anon' ],
                                [],
-                               [ 'rc_user = 0' ],
-                               [],
-                               [],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_NOT_ANON ] ],
                                null,
+                               [ 'actormigration' => 'table' ],
                                [],
+                               [ 'actormigration is not anon' ],
                                [],
-                               [ 'rc_user != 0' ],
-                               [],
-                               [],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'filters' => [ WatchedItemQueryService::FILTER_PATROLLED ] ],
@@ -759,20 +801,20 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                        [
                                [ 'onlyByUser' => 'SomeOtherUser' ],
                                null,
+                               [ 'actormigration' => 'table' ],
                                [],
+                               [ 'actormigration_conds' ],
                                [],
-                               [ 'rc_user_text' => 'SomeOtherUser' ],
-                               [],
-                               [],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'notByUser' => 'SomeOtherUser' ],
                                null,
+                               [ 'actormigration' => 'table' ],
                                [],
+                               [ 'NOT(actormigration_conds)' ],
                                [],
-                               [ "rc_user_text != 'SomeOtherUser'" ],
-                               [],
-                               [],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'dir' => WatchedItemQueryService::DIR_OLDER ],
@@ -984,62 +1026,74 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                        [
                                [],
                                'deletedhistory',
+                               [],
                                [
                                        '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
                                                LogPage::DELETED_ACTION . ')'
                                ],
+                               [],
                        ],
                        [
                                [],
                                'suppressrevision',
+                               [],
                                [
                                        '(rc_type != ' . RC_LOG . ') OR (' .
                                                '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
                                                ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
                                ],
+                               [],
                        ],
                        [
                                [],
                                'viewsuppressed',
+                               [],
                                [
                                        '(rc_type != ' . RC_LOG . ') OR (' .
                                                '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
                                                ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
                                ],
+                               [],
                        ],
                        [
                                [ 'onlyByUser' => 'SomeOtherUser' ],
                                'deletedhistory',
+                               [ 'actormigration' => 'table' ],
                                [
-                                       'rc_user_text' => 'SomeOtherUser',
+                                       'actormigration_conds',
                                        '(rc_deleted & ' . Revision::DELETED_USER . ') != ' . Revision::DELETED_USER,
                                        '(rc_type != ' . RC_LOG . ') OR ((rc_deleted & ' . LogPage::DELETED_ACTION . ') != ' .
                                                LogPage::DELETED_ACTION . ')'
                                ],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'onlyByUser' => 'SomeOtherUser' ],
                                'suppressrevision',
+                               [ 'actormigration' => 'table' ],
                                [
-                                       'rc_user_text' => 'SomeOtherUser',
+                                       'actormigration_conds',
                                        '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
                                                ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
                                        '(rc_type != ' . RC_LOG . ') OR (' .
                                                '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
                                                ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
                                ],
+                               [ 'actormigration' => 'join' ],
                        ],
                        [
                                [ 'onlyByUser' => 'SomeOtherUser' ],
                                'viewsuppressed',
+                               [ 'actormigration' => 'table' ],
                                [
-                                       'rc_user_text' => 'SomeOtherUser',
+                                       'actormigration_conds',
                                        '(rc_deleted & ' . ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ) . ') != ' .
                                                ( Revision::DELETED_USER | Revision::DELETED_RESTRICTED ),
                                        '(rc_type != ' . RC_LOG . ') OR (' .
                                                '(rc_deleted & ' . ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ') != ' .
                                                ( LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED ) . ')'
                                ],
+                               [ 'actormigration' => 'join' ],
                        ],
                ];
        }
@@ -1050,7 +1104,9 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
        public function testGetWatchedItemsWithRecentChangeInfo_userPermissionRelatedExtraChecks(
                array $options,
                $notAllowedAction,
-               array $expectedExtraConds
+               array $expectedExtraTables,
+               array $expectedExtraConds,
+               array $expectedExtraJoins
        ) {
                $commonConds = [ 'wl_user' => 1, '(rc_this_oldid=page_latest) OR (rc_type=3)' ];
                $conds = array_merge( $commonConds, $expectedExtraConds );
@@ -1059,12 +1115,15 @@ class WatchedItemQueryServiceUnitTest extends MediaWikiTestCase {
                $mockDb->expects( $this->once() )
                        ->method( 'select' )
                        ->with(
-                               [ 'recentchanges', 'watchlist', 'page' ],
+                               array_merge( [ 'recentchanges', 'watchlist', 'page' ], $expectedExtraTables ),
                                $this->isType( 'array' ),
                                $conds,
                                $this->isType( 'string' ),
                                $this->isType( 'array' ),
-                               $this->isType( 'array' )
+                               array_merge( [
+                                       'watchlist' => [ 'INNER JOIN', [ 'wl_namespace=rc_namespace', 'wl_title=rc_title' ] ],
+                                       'page' => [ 'LEFT JOIN', 'rc_cur_id=page_id' ],
+                               ], $expectedExtraJoins )
                        )
                        ->will( $this->returnValue( [] ) );
 
index 3372bf0..785e114 100644 (file)
@@ -45,12 +45,12 @@ return [
                'scripts' => [
                        'tests/qunit/suites/resources/startup.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.accessKeyLabel.test.js',
-                       'tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.color.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.colorUtil.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.getAttrs.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.hidpi.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.highlightText.test.js',
+                       'tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.localize.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.makeCollapsible.test.js',
                        'tests/qunit/suites/resources/jquery/jquery.tabIndex.test.js',
@@ -103,12 +103,12 @@ return [
                ],
                'dependencies' => [
                        'jquery.accessKeyLabel',
-                       'jquery.byteLimit',
                        'jquery.color',
                        'jquery.colorUtil',
                        'jquery.getAttrs',
                        'jquery.hidpi',
                        'jquery.highlightText',
+                       'jquery.lengthLimit',
                        'jquery.localize',
                        'jquery.makeCollapsible',
                        'jquery.tabIndex',
diff --git a/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js b/tests/qunit/suites/resources/jquery/jquery.byteLimit.test.js
deleted file mode 100644 (file)
index d3233da..0000000
+++ /dev/null
@@ -1,286 +0,0 @@
-( function ( $, mw ) {
-       var simpleSample, U_20AC, poop, mbSample;
-
-       QUnit.module( 'jquery.byteLimit', QUnit.newMwEnvironment() );
-
-       // Simple sample (20 chars, 20 bytes)
-       simpleSample = '12345678901234567890';
-
-       // 3 bytes (euro-symbol)
-       U_20AC = '\u20AC';
-
-       // Outside of the BMP (pile of poo emoji)
-       poop = '\uD83D\uDCA9'; // "💩"
-
-       // Multi-byte sample (22 chars, 26 bytes)
-       mbSample = '1234567890' + U_20AC + '1234567890' + U_20AC;
-
-       // Basic sendkey-implementation
-       function addChars( $input, charstr ) {
-               var c, len;
-
-               function x( $input, i ) {
-                       // Add character to the value
-                       return $input.val() + charstr.charAt( i );
-               }
-
-               for ( c = 0, len = charstr.length; c < len; c += 1 ) {
-                       $input
-                               .val( x( $input, c ) )
-                               .trigger( 'change' );
-               }
-       }
-
-       /**
-        * Test factory for $.fn.byteLimit
-        *
-        * @param {Object} options
-        * @param {string} options.description Test name
-        * @param {jQuery} options.$input jQuery object in an input element
-        * @param {string} options.sample Sequence of characters to simulate being
-        *  added one by one
-        * @param {string} options.expected Expected final value of `$input`
-        */
-       function byteLimitTest( options ) {
-               var opt = $.extend( {
-                       description: '',
-                       $input: null,
-                       sample: '',
-                       expected: ''
-               }, options );
-
-               QUnit.test( opt.description, function ( assert ) {
-                       opt.$input.appendTo( '#qunit-fixture' );
-
-                       // Simulate pressing keys for each of the sample characters
-                       addChars( opt.$input, opt.sample );
-
-                       assert.equal(
-                               opt.$input.val(),
-                               opt.expected,
-                               'New value matches the expected string'
-                       );
-               } );
-       }
-
-       byteLimitTest( {
-               description: 'Plain text input',
-               $input: $( '<input>' ).attr( 'type', 'text' ),
-               sample: simpleSample,
-               expected: simpleSample
-       } );
-
-       byteLimitTest( {
-               description: 'Plain text input. Calling byteLimit with no parameters and no maxlength attribute (T38310)',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit(),
-               sample: simpleSample,
-               expected: simpleSample
-       } );
-
-       byteLimitTest( {
-               description: 'Limit using the maxlength attribute',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .attr( 'maxlength', '10' )
-                       .byteLimit(),
-               sample: simpleSample,
-               expected: '1234567890'
-       } );
-
-       byteLimitTest( {
-               description: 'Limit using a custom value',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 10 ),
-               sample: simpleSample,
-               expected: '1234567890'
-       } );
-
-       byteLimitTest( {
-               description: 'Limit using a custom value, overriding maxlength attribute',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .attr( 'maxlength', '10' )
-                       .byteLimit( 15 ),
-               sample: simpleSample,
-               expected: '123456789012345'
-       } );
-
-       byteLimitTest( {
-               description: 'Limit using a custom value (multibyte)',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 14 ),
-               sample: mbSample,
-               expected: '1234567890' + U_20AC + '1'
-       } );
-
-       byteLimitTest( {
-               description: 'Limit using a custom value (multibyte, outside BMP)',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 3 ),
-               sample: poop,
-               expected: ''
-       } );
-
-       byteLimitTest( {
-               description: 'Limit using a custom value (multibyte) overlapping a byte',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 12 ),
-               sample: mbSample,
-               expected: '123456789012'
-       } );
-
-       byteLimitTest( {
-               description: 'Pass the limit and a callback as input filter',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 6, function ( val ) {
-                               var title = mw.Title.newFromText( String( val ) );
-                               // Return without namespace prefix
-                               return title ? title.getMain() : '';
-                       } ),
-               sample: 'User:Sample',
-               expected: 'User:Sample'
-       } );
-
-       byteLimitTest( {
-               description: 'Limit using the maxlength attribute and pass a callback as input filter',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .attr( 'maxlength', '6' )
-                       .byteLimit( function ( val ) {
-                               var title = mw.Title.newFromText( String( val ) );
-                               // Return without namespace prefix
-                               return title ? title.getMain() : '';
-                       } ),
-               sample: 'User:Sample',
-               expected: 'User:Sample'
-       } );
-
-       byteLimitTest( {
-               description: 'Pass the limit and a callback as input filter',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 6, function ( val ) {
-                               var title = mw.Title.newFromText( String( val ) );
-                               // Return without namespace prefix
-                               return title ? title.getMain() : '';
-                       } ),
-               sample: 'User:Example',
-               // The callback alters the value to be used to calculeate
-               // the length. The altered value is "Exampl" which has
-               // a length of 6, the "e" would exceed the limit.
-               expected: 'User:Exampl'
-       } );
-
-       byteLimitTest( {
-               description: 'Input filter that increases the length',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 10, function ( text ) {
-                               return 'prefix' + text;
-                       } ),
-               sample: simpleSample,
-               // Prefix adds 6 characters, limit is reached after 4
-               expected: '1234'
-       } );
-
-       // Regression tests for T43450
-       byteLimitTest( {
-               description: 'Input filter of which the base exceeds the limit',
-               $input: $( '<input>' ).attr( 'type', 'text' )
-                       .byteLimit( 3, function ( text ) {
-                               return 'prefix' + text;
-                       } ),
-               sample: simpleSample,
-               expected: ''
-       } );
-
-       QUnit.test( 'Confirm properties and attributes set', function ( assert ) {
-               var $el;
-
-               $el = $( '<input>' ).attr( 'type', 'text' )
-                       .attr( 'maxlength', '7' )
-                       .appendTo( '#qunit-fixture' )
-                       .byteLimit();
-
-               assert.strictEqual( $el.attr( 'maxlength' ), '7', 'maxlength attribute unchanged for simple limit' );
-
-               $el = $( '<input>' ).attr( 'type', 'text' )
-                       .attr( 'maxlength', '7' )
-                       .appendTo( '#qunit-fixture' )
-                       .byteLimit( 12 );
-
-               assert.strictEqual( $el.attr( 'maxlength' ), '12', 'maxlength attribute updated for custom limit' );
-
-               $el = $( '<input>' ).attr( 'type', 'text' )
-                       .attr( 'maxlength', '7' )
-                       .appendTo( '#qunit-fixture' )
-                       .byteLimit( 12, function ( val ) {
-                               return val;
-                       } );
-
-               assert.strictEqual( $el.attr( 'maxlength' ), undefined, 'maxlength attribute removed for limit with callback' );
-
-               $( '<input>' ).attr( 'type', 'text' )
-                       .addClass( 'mw-test-byteLimit-foo' )
-                       .attr( 'maxlength', '7' )
-                       .appendTo( '#qunit-fixture' );
-
-               $( '<input>' ).attr( 'type', 'text' )
-                       .addClass( 'mw-test-byteLimit-foo' )
-                       .attr( 'maxlength', '12' )
-                       .appendTo( '#qunit-fixture' );
-
-               $el = $( '.mw-test-byteLimit-foo' );
-
-               assert.strictEqual( $el.length, 2, 'Verify that there are no other elements clashing with this test suite' );
-
-               $el.byteLimit();
-       } );
-
-       QUnit.test( 'Trim from insertion when limit exceeded', function ( assert ) {
-               var $el;
-
-               // Use a new <input> because the bug only occurs on the first time
-               // the limit it reached (T42850)
-               $el = $( '<input>' ).attr( 'type', 'text' )
-                       .appendTo( '#qunit-fixture' )
-                       .byteLimit( 3 )
-                       .val( 'abc' ).trigger( 'change' )
-                       .val( 'zabc' ).trigger( 'change' );
-
-               assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 0), not the end' );
-
-               $el = $( '<input>' ).attr( 'type', 'text' )
-                       .appendTo( '#qunit-fixture' )
-                       .byteLimit( 3 )
-                       .val( 'abc' ).trigger( 'change' )
-                       .val( 'azbc' ).trigger( 'change' );
-
-               assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 1), not the end' );
-       } );
-
-       QUnit.test( 'Do not cut up false matching substrings in emoji insertions', function ( assert ) {
-               var $el,
-                       oldVal = '\uD83D\uDCA9\uD83D\uDCA9', // "💩💩"
-                       newVal = '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9\uD83D\uDCA9', // "💩💹🢩💩"
-                       expected = '\uD83D\uDCA9\uD83D\uDCB9\uD83D\uDCA9'; // "💩💹💩"
-
-               // Possible bad results:
-               // * With no surrogate support:
-               //   '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9' "💩💹🢩"
-               // * With correct trimming but bad detection of inserted text:
-               //   '\uD83D\uDCA9\uD83D\uDCB9\uDCA9' "💩💹�"
-
-               $el = $( '<input>' ).attr( 'type', 'text' )
-                       .appendTo( '#qunit-fixture' )
-                       .byteLimit( 12 )
-                       .val( oldVal ).trigger( 'change' )
-                       .val( newVal ).trigger( 'change' );
-
-               assert.strictEqual( $el.val(), expected, 'Pasted emoji correctly trimmed at the end' );
-       } );
-
-       byteLimitTest( {
-               description: 'Unpaired surrogates do not crash',
-               $input: $( '<input>' ).attr( 'type', 'text' ).byteLimit( 4 ),
-               sample: '\uD800\uD800\uDFFF',
-               expected: '\uD800'
-       } );
-
-}( jQuery, mediaWiki ) );
diff --git a/tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js b/tests/qunit/suites/resources/jquery/jquery.lengthLimit.test.js
new file mode 100644 (file)
index 0000000..7117d1f
--- /dev/null
@@ -0,0 +1,286 @@
+( function ( $, mw ) {
+       var simpleSample, U_20AC, poop, mbSample;
+
+       QUnit.module( 'jquery.lengthLimit', QUnit.newMwEnvironment() );
+
+       // Simple sample (20 chars, 20 bytes)
+       simpleSample = '12345678901234567890';
+
+       // 3 bytes (euro-symbol)
+       U_20AC = '\u20AC';
+
+       // Outside of the BMP (pile of poo emoji)
+       poop = '\uD83D\uDCA9'; // "💩"
+
+       // Multi-byte sample (22 chars, 26 bytes)
+       mbSample = '1234567890' + U_20AC + '1234567890' + U_20AC;
+
+       // Basic sendkey-implementation
+       function addChars( $input, charstr ) {
+               var c, len;
+
+               function x( $input, i ) {
+                       // Add character to the value
+                       return $input.val() + charstr.charAt( i );
+               }
+
+               for ( c = 0, len = charstr.length; c < len; c += 1 ) {
+                       $input
+                               .val( x( $input, c ) )
+                               .trigger( 'change' );
+               }
+       }
+
+       /**
+        * Test factory for $.fn.byteLimit
+        *
+        * @param {Object} options
+        * @param {string} options.description Test name
+        * @param {jQuery} options.$input jQuery object in an input element
+        * @param {string} options.sample Sequence of characters to simulate being
+        *  added one by one
+        * @param {string} options.expected Expected final value of `$input`
+        */
+       function byteLimitTest( options ) {
+               var opt = $.extend( {
+                       description: '',
+                       $input: null,
+                       sample: '',
+                       expected: ''
+               }, options );
+
+               QUnit.test( opt.description, function ( assert ) {
+                       opt.$input.appendTo( '#qunit-fixture' );
+
+                       // Simulate pressing keys for each of the sample characters
+                       addChars( opt.$input, opt.sample );
+
+                       assert.equal(
+                               opt.$input.val(),
+                               opt.expected,
+                               'New value matches the expected string'
+                       );
+               } );
+       }
+
+       byteLimitTest( {
+               description: 'Plain text input',
+               $input: $( '<input>' ).attr( 'type', 'text' ),
+               sample: simpleSample,
+               expected: simpleSample
+       } );
+
+       byteLimitTest( {
+               description: 'Plain text input. Calling byteLimit with no parameters and no maxlength attribute (T38310)',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit(),
+               sample: simpleSample,
+               expected: simpleSample
+       } );
+
+       byteLimitTest( {
+               description: 'Limit using the maxlength attribute',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .attr( 'maxlength', '10' )
+                       .byteLimit(),
+               sample: simpleSample,
+               expected: '1234567890'
+       } );
+
+       byteLimitTest( {
+               description: 'Limit using a custom value',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 10 ),
+               sample: simpleSample,
+               expected: '1234567890'
+       } );
+
+       byteLimitTest( {
+               description: 'Limit using a custom value, overriding maxlength attribute',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .attr( 'maxlength', '10' )
+                       .byteLimit( 15 ),
+               sample: simpleSample,
+               expected: '123456789012345'
+       } );
+
+       byteLimitTest( {
+               description: 'Limit using a custom value (multibyte)',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 14 ),
+               sample: mbSample,
+               expected: '1234567890' + U_20AC + '1'
+       } );
+
+       byteLimitTest( {
+               description: 'Limit using a custom value (multibyte, outside BMP)',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 3 ),
+               sample: poop,
+               expected: ''
+       } );
+
+       byteLimitTest( {
+               description: 'Limit using a custom value (multibyte) overlapping a byte',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 12 ),
+               sample: mbSample,
+               expected: '123456789012'
+       } );
+
+       byteLimitTest( {
+               description: 'Pass the limit and a callback as input filter',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 6, function ( val ) {
+                               var title = mw.Title.newFromText( String( val ) );
+                               // Return without namespace prefix
+                               return title ? title.getMain() : '';
+                       } ),
+               sample: 'User:Sample',
+               expected: 'User:Sample'
+       } );
+
+       byteLimitTest( {
+               description: 'Limit using the maxlength attribute and pass a callback as input filter',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .attr( 'maxlength', '6' )
+                       .byteLimit( function ( val ) {
+                               var title = mw.Title.newFromText( String( val ) );
+                               // Return without namespace prefix
+                               return title ? title.getMain() : '';
+                       } ),
+               sample: 'User:Sample',
+               expected: 'User:Sample'
+       } );
+
+       byteLimitTest( {
+               description: 'Pass the limit and a callback as input filter',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 6, function ( val ) {
+                               var title = mw.Title.newFromText( String( val ) );
+                               // Return without namespace prefix
+                               return title ? title.getMain() : '';
+                       } ),
+               sample: 'User:Example',
+               // The callback alters the value to be used to calculeate
+               // the length. The altered value is "Exampl" which has
+               // a length of 6, the "e" would exceed the limit.
+               expected: 'User:Exampl'
+       } );
+
+       byteLimitTest( {
+               description: 'Input filter that increases the length',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 10, function ( text ) {
+                               return 'prefix' + text;
+                       } ),
+               sample: simpleSample,
+               // Prefix adds 6 characters, limit is reached after 4
+               expected: '1234'
+       } );
+
+       // Regression tests for T43450
+       byteLimitTest( {
+               description: 'Input filter of which the base exceeds the limit',
+               $input: $( '<input>' ).attr( 'type', 'text' )
+                       .byteLimit( 3, function ( text ) {
+                               return 'prefix' + text;
+                       } ),
+               sample: simpleSample,
+               expected: ''
+       } );
+
+       QUnit.test( 'Confirm properties and attributes set', function ( assert ) {
+               var $el;
+
+               $el = $( '<input>' ).attr( 'type', 'text' )
+                       .attr( 'maxlength', '7' )
+                       .appendTo( '#qunit-fixture' )
+                       .byteLimit();
+
+               assert.strictEqual( $el.attr( 'maxlength' ), '7', 'maxlength attribute unchanged for simple limit' );
+
+               $el = $( '<input>' ).attr( 'type', 'text' )
+                       .attr( 'maxlength', '7' )
+                       .appendTo( '#qunit-fixture' )
+                       .byteLimit( 12 );
+
+               assert.strictEqual( $el.attr( 'maxlength' ), '12', 'maxlength attribute updated for custom limit' );
+
+               $el = $( '<input>' ).attr( 'type', 'text' )
+                       .attr( 'maxlength', '7' )
+                       .appendTo( '#qunit-fixture' )
+                       .byteLimit( 12, function ( val ) {
+                               return val;
+                       } );
+
+               assert.strictEqual( $el.attr( 'maxlength' ), undefined, 'maxlength attribute removed for limit with callback' );
+
+               $( '<input>' ).attr( 'type', 'text' )
+                       .addClass( 'mw-test-byteLimit-foo' )
+                       .attr( 'maxlength', '7' )
+                       .appendTo( '#qunit-fixture' );
+
+               $( '<input>' ).attr( 'type', 'text' )
+                       .addClass( 'mw-test-byteLimit-foo' )
+                       .attr( 'maxlength', '12' )
+                       .appendTo( '#qunit-fixture' );
+
+               $el = $( '.mw-test-byteLimit-foo' );
+
+               assert.strictEqual( $el.length, 2, 'Verify that there are no other elements clashing with this test suite' );
+
+               $el.byteLimit();
+       } );
+
+       QUnit.test( 'Trim from insertion when limit exceeded', function ( assert ) {
+               var $el;
+
+               // Use a new <input> because the bug only occurs on the first time
+               // the limit it reached (T42850)
+               $el = $( '<input>' ).attr( 'type', 'text' )
+                       .appendTo( '#qunit-fixture' )
+                       .byteLimit( 3 )
+                       .val( 'abc' ).trigger( 'change' )
+                       .val( 'zabc' ).trigger( 'change' );
+
+               assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 0), not the end' );
+
+               $el = $( '<input>' ).attr( 'type', 'text' )
+                       .appendTo( '#qunit-fixture' )
+                       .byteLimit( 3 )
+                       .val( 'abc' ).trigger( 'change' )
+                       .val( 'azbc' ).trigger( 'change' );
+
+               assert.strictEqual( $el.val(), 'abc', 'Trim from the insertion point (at 1), not the end' );
+       } );
+
+       QUnit.test( 'Do not cut up false matching substrings in emoji insertions', function ( assert ) {
+               var $el,
+                       oldVal = '\uD83D\uDCA9\uD83D\uDCA9', // "💩💩"
+                       newVal = '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9\uD83D\uDCA9', // "💩💹🢩💩"
+                       expected = '\uD83D\uDCA9\uD83D\uDCB9\uD83D\uDCA9'; // "💩💹💩"
+
+               // Possible bad results:
+               // * With no surrogate support:
+               //   '\uD83D\uDCA9\uD83D\uDCB9\uD83E\uDCA9' "💩💹🢩"
+               // * With correct trimming but bad detection of inserted text:
+               //   '\uD83D\uDCA9\uD83D\uDCB9\uDCA9' "💩💹�"
+
+               $el = $( '<input>' ).attr( 'type', 'text' )
+                       .appendTo( '#qunit-fixture' )
+                       .byteLimit( 12 )
+                       .val( oldVal ).trigger( 'change' )
+                       .val( newVal ).trigger( 'change' );
+
+               assert.strictEqual( $el.val(), expected, 'Pasted emoji correctly trimmed at the end' );
+       } );
+
+       byteLimitTest( {
+               description: 'Unpaired surrogates do not crash',
+               $input: $( '<input>' ).attr( 'type', 'text' ).byteLimit( 4 ),
+               sample: '\uD800\uD800\uDFFF',
+               expected: '\uD800'
+       } );
+
+}( jQuery, mediaWiki ) );